import type { ApiCallResult } from "./http";
import type { PasskeyAuthenticatorAttachment } from "./passkey";
import {
  buildPasskeyRenameDrafts,
  parsePasskeyList,
  type ListPasskeysResult,
} from "./passkey-types";

type PasskeyEndpointCaller = <TBody = unknown>(
  path: string,
  init?: RequestInit,
) => Promise<ApiCallResult<TBody>>;

type PasskeyCredentialAdapter = {
  createCredential: (
    optionsJson: unknown,
    authenticatorAttachment?: PasskeyAuthenticatorAttachment,
  ) => Promise<Record<string, unknown>>;
  getCredential: (optionsJson: unknown) => Promise<Record<string, unknown>>;
};

export type PasskeyModuleDependencies = {
  callEndpoint: PasskeyEndpointCaller;
  credentialAdapter: PasskeyCredentialAdapter;
};

export type PasskeyRegisterInput = {
  passkeyName: string;
  authenticatorAttachment: PasskeyAuthenticatorAttachment;
};

export class PasskeyModule {
  constructor(private readonly deps: PasskeyModuleDependencies) {}

  buildRenameDrafts(
    passkeys: ReturnType<typeof parsePasskeyList>,
    currentDrafts: Record<string, string>,
  ): Record<string, string> {
    return buildPasskeyRenameDrafts(passkeys, currentDrafts);
  }

  async listUserPasskeys(): Promise<ListPasskeysResult> {
    const result = await this.deps.callEndpoint<unknown[]>(
      "/passkey/list-user-passkeys",
      {
        method: "GET",
      },
    );

    if (!result.ok) {
      return {
        ok: false,
        status: result.status,
        message: result.message || `Failed to list passkeys (${result.status}).`,
      };
    }

    return {
      ok: true,
      status: result.status,
      passkeys: parsePasskeyList(result.body),
    };
  }

  async registerPasskey(args: PasskeyRegisterInput): Promise<unknown> {
    const registerOptionsResult = await this.deps.callEndpoint(
      `/passkey/generate-register-options?authenticatorAttachment=${encodeURIComponent(args.authenticatorAttachment)}`,
      { method: "GET" },
    );
    this.ensureApiOk(registerOptionsResult, "Failed to start passkey registration");

    const credentialResponse = await this.deps.credentialAdapter.createCredential(
      registerOptionsResult.body,
      args.authenticatorAttachment,
    );
    const verifyResult = await this.deps.callEndpoint(
      "/passkey/verify-registration",
      {
        method: "POST",
        body: JSON.stringify({
          response: credentialResponse,
          name: args.passkeyName.trim() || undefined,
        }),
      },
    );

    this.ensureApiOk(verifyResult, "Failed to verify passkey registration");
    return verifyResult.body ?? verifyResult;
  }

  async authenticateWithPasskey(): Promise<unknown> {
    const optionsResult = await this.deps.callEndpoint(
      "/passkey/generate-authenticate-options",
      {
        method: "POST",
        body: JSON.stringify({}),
      },
    );
    this.ensureApiOk(optionsResult, "Failed to start passkey authentication");

    const credentialResponse = await this.deps.credentialAdapter.getCredential(
      optionsResult.body,
    );
    const verifyResult = await this.deps.callEndpoint(
      "/passkey/verify-authentication",
      {
        method: "POST",
        body: JSON.stringify({ response: credentialResponse }),
      },
    );

    this.ensureApiOk(verifyResult, "Failed to verify passkey authentication");
    return verifyResult.body ?? verifyResult;
  }

  async renamePasskey(id: string, name: string): Promise<unknown> {
    const result = await this.deps.callEndpoint("/passkey/update-passkey", {
      method: "POST",
      body: JSON.stringify({ id, name }),
    });
    this.ensureApiOk(result, "Failed to rename passkey");
    return result.body ?? result;
  }

  async deletePasskey(id: string): Promise<unknown> {
    const result = await this.deps.callEndpoint("/passkey/delete-passkey", {
      method: "POST",
      body: JSON.stringify({ id }),
    });
    this.ensureApiOk(result, "Failed to delete passkey");
    return result.body ?? result;
  }

  private ensureApiOk(
    result: { ok: boolean; status: number; message: string },
    fallbackMessage: string,
  ): void {
    if (result.ok) return;
    throw new Error(result.message || `${fallbackMessage} (${result.status}).`);
  }
}
