import crypto from "node:crypto";
import type { SanitizedConfig } from "payload";
import type { IAuthenticationService } from "../../application/services/authentication.service.interface";
import type { Cookie } from "../../entities/models/cookie";
import type { Session } from "../../entities/models/session";
import type { User } from "../../entities/models/user";

// ---------------------------------------------------------------------------
// Deferred methods
// ---------------------------------------------------------------------------
// `createSession`, `validateSession`, and `invalidateSession` require Payload's
// internal JWT-based auth session machinery, which does not map cleanly to a
// generic session interface without deep integration with Payload's REST/local
// API and cookie infrastructure.
//
// TODO: Implement these three methods once the session
// cookie strategy is settled. Until then they throw NotImplementedError to
// keep the production-shaped file in place without silently no-oping.
//
// The mock (`authentication.service.mock.ts`) handles all test paths.

class NotImplementedError extends Error {
  constructor(method: string) {
    super(`NotImplemented: AuthenticationService.${method}`);
    this.name = "NotImplementedError";
  }
}

const SALT_LENGTH = 16;
const KEY_LENGTH = 64;
const ITERATIONS = 100_000;
const DIGEST = "sha512";
const SEPARATOR = ":";

export class AuthenticationService implements IAuthenticationService {
  constructor(private _config: SanitizedConfig) {}

  generateUserId(): string {
    return crypto.randomUUID();
  }

  async hashPassword(password: string): Promise<string> {
    const salt = crypto.randomBytes(SALT_LENGTH).toString("hex");
    const hash = await new Promise<string>((resolve, reject) => {
      crypto.pbkdf2(
        password,
        salt,
        ITERATIONS,
        KEY_LENGTH,
        DIGEST,
        (err, derivedKey) => {
          if (err) reject(err);
          else resolve(derivedKey.toString("hex"));
        },
      );
    });
    return `${salt}${SEPARATOR}${hash}`;
  }

  async verifyPassword(storedHash: string, password: string): Promise<boolean> {
    const parts = storedHash.split(SEPARATOR);
    if (parts.length !== 2) return false;
    const salt = parts[0]!;
    const expectedHash = parts[1]!;
    const actualHash = await new Promise<string>((resolve, reject) => {
      crypto.pbkdf2(
        password,
        salt,
        ITERATIONS,
        KEY_LENGTH,
        DIGEST,
        (err, derivedKey) => {
          if (err) reject(err);
          else resolve(derivedKey.toString("hex"));
        },
      );
    });
    return crypto.timingSafeEqual(
      Buffer.from(expectedHash, "hex"),
      Buffer.from(actualHash, "hex"),
    );
  }

  // TODO: Implement using Payload's local.login / JWT session issuance.
  // Payload creates sessions via its REST auth endpoint; mapping that to a
  // generic { session: Session; cookie: Cookie } shape requires understanding
  // the JWT payload structure and the cookie name/attributes Payload uses.
  async createSession(
    _user: User,
  ): Promise<{ session: Session; cookie: Cookie }> {
    throw new NotImplementedError("createSession");
  }

  // TODO: Implement using Payload's JWT verify mechanism.
  // Need to call Payload's local API to verify the token and retrieve the user.
  async validateSession(
    _sessionId: string,
  ): Promise<{ user: User; session: Session }> {
    throw new NotImplementedError("validateSession");
  }

  // TODO: Implement by clearing the session token.
  // Payload does not have a server-side session store by default; invalidation
  // is typically done client-side by clearing the cookie.
  async invalidateSession(
    _sessionId: string,
  ): Promise<{ blankCookie: Cookie }> {
    throw new NotImplementedError("invalidateSession");
  }
}
