import { useState, useCallback } from 'react';
/* global PublicKeyCredentialCreationOptions, CredentialCreationOptions */

import {
  isWebAuthnAvailable,
  preparePublicKeyCreationOptions,
  serializeRegistrationCredential,
} from '../utils/webauthn';
import {
  deletePasskey,
  finishPasskeyRegistration,
  listPasskeys,
  startPasskeyRegistration,
} from '../lib/apiClient';
import type { PasskeyId } from '../types/identifiers';

type StatusMessageFn = (message: string, variant?: string) => void;
type NotifyApiErrorFn = (error: unknown, message: string) => void;

type ApiError = {
  response?: {
    status?: number;
    data?: {
      error?: string;
    };
  };
  name?: string;
};

export interface PasskeyRecord {
  id?: PasskeyId;
  nickname?: string;
  created_at?: string;
  createdAt?: string;
  last_used_at?: string;
  lastUsedAt?: string;
  revoked_at?: string;
  revokedAt?: string;
  revoked_reason?: string;
  revokedReason?: string;
  transports?: string[];
  [key: string]: unknown;
}

interface PasskeyChallengeResponse {
  challengeId?: string;
  challenge_id?: string;
  publicKey?: PublicKeyCredentialCreationOptions;
  public_key?: PublicKeyCredentialCreationOptions;
  challenge?: {
    publicKey?: PublicKeyCredentialCreationOptions;
  };
  publicKeyCredentialCreationOptions?: PublicKeyCredentialCreationOptions;
}

interface PasskeyRegisterPayload {
  challengeId: string;
  credential: unknown;
  nickname?: string;
}

export type RegisterPasskeyFailureReason = 'unsupported' | 'busy' | 'cancelled' | 'error';
export type RegisterPasskeyResult =
  | { ok: true }
  | { ok: false; reason: RegisterPasskeyFailureReason; message?: string };

export type RevokePasskeyFailureReason = 'missing-id' | 'error';
export type RevokePasskeyResult =
  | { ok: true }
  | { ok: false; reason: RevokePasskeyFailureReason; message?: string };

interface UsePasskeysArgs {
  notifyApiError: NotifyApiErrorFn;
  setStatusMessage: StatusMessageFn;
  token?: string | null;
}

interface UsePasskeysResult {
  passkeys: PasskeyRecord[];
  passkeysSupported: boolean | null;
  passkeysLoading: boolean;
  registeringPasskey: boolean;
  revokingPasskeyId: PasskeyId | null;
  refreshPasskeys: () => Promise<void>;
  registerPasskey: (options?: { nickname?: string }) => Promise<RegisterPasskeyResult>;
  revokePasskey: (
    passkeyId: PasskeyId,
    reason?: string,
  ) => Promise<RevokePasskeyResult>;
}

const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArgs): UsePasskeysResult => {
  const [passkeys, setPasskeys] = useState<PasskeyRecord[]>([]);
  const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
  const [passkeysLoading, setPasskeysLoading] = useState(false);
  const [registeringPasskey, setRegisteringPasskey] = useState(false);
  const [revokingPasskeyId, setRevokingPasskeyId] = useState<PasskeyId | null>(null);

  const refreshPasskeys = useCallback(async (): Promise<void> => {
    if (!token) {
      return;
    }
    setPasskeysLoading(true);
    try {
      const passkeyData = await listPasskeys();
      setPasskeys(passkeyData);
      setPasskeysSupported(true);
    } catch (error) {
      const status = (error as ApiError)?.response?.status;
      if (status === 400 || status === 404) {
        setPasskeysSupported(false);
        setPasskeys([]);
      } else {
        notifyApiError(error, 'Failed to load passkeys.');
      }
    } finally {
      setPasskeysLoading(false);
    }
  }, [notifyApiError, token]);

  const registerPasskey = useCallback(
    async ({ nickname }: { nickname?: string } = {}): Promise<RegisterPasskeyResult> => {
      if (!isWebAuthnAvailable()) {
        setPasskeysSupported(false);
        setStatusMessage('Passkeys are not supported in this browser.', 'error');
        return { ok: false, reason: 'unsupported' };
      }
      if (registeringPasskey) {
        return { ok: false, reason: 'busy' };
      }

      setRegisteringPasskey(true);
      try {
        const data = await startPasskeyRegistration();
        const challengeData = data as PasskeyChallengeResponse | undefined;
        const challengeId = challengeData?.challengeId || challengeData?.challenge_id;
        const publicKeyOptions =
          challengeData?.publicKey
          || challengeData?.public_key
          || challengeData?.challenge?.publicKey
          || challengeData?.publicKeyCredentialCreationOptions;

        if (!challengeId || !publicKeyOptions) {
          throw new Error('Invalid passkey challenge response.');
        }

        const publicKey = preparePublicKeyCreationOptions({ publicKey: publicKeyOptions });
        const credential = (await navigator.credentials.create({
          publicKey,
        } as CredentialCreationOptions)) as PublicKeyCredential | null;

        if (!credential) {
          return { ok: false, reason: 'cancelled' };
        }

        const serialized = serializeRegistrationCredential(credential);
        const payload: PasskeyRegisterPayload = {
          challengeId,
          credential: serialized,
        };
        const trimmedNickname = nickname?.trim?.();
        if (trimmedNickname) {
          payload.nickname = trimmedNickname;
        }

        await finishPasskeyRegistration(payload);
        await refreshPasskeys();
        setPasskeysSupported(true);
        setStatusMessage('Passkey registered.', 'success');
        return { ok: true };
      } catch (error) {
        const typedError = error as ApiError;
        if (typedError?.name === 'NotAllowedError') {
          setStatusMessage('Passkey registration cancelled.', 'info');
          return { ok: false, reason: 'cancelled' };
        }

        const status = typedError?.response?.status;
        if (status === 400 || status === 404) {
          setPasskeysSupported(false);
        }

        const message = typedError?.response?.data?.error || 'Failed to register passkey.';
        notifyApiError(error, message);
        return { ok: false, reason: 'error', message };
      } finally {
        setRegisteringPasskey(false);
      }
    },
    [notifyApiError, refreshPasskeys, registeringPasskey, setStatusMessage],
  );

  const revokePasskey = useCallback(
    async (
      passkeyId: PasskeyId,
      reason?: string,
    ): Promise<RevokePasskeyResult> => {
      if (passkeyId == null) {
        return { ok: false, reason: 'missing-id' };
      }
      setRevokingPasskeyId(passkeyId);
      try {
        await deletePasskey(passkeyId, { reason });
        await refreshPasskeys();
        setStatusMessage('Passkey revoked.', 'success');
        return { ok: true };
      } catch (error) {
        const message = (error as ApiError)?.response?.data?.error || 'Failed to revoke passkey.';
        notifyApiError(error, message);
        return { ok: false, reason: 'error', message };
      } finally {
        setRevokingPasskeyId(null);
      }
    },
    [notifyApiError, refreshPasskeys, setStatusMessage],
  );

  return {
    passkeys,
    passkeysSupported,
    passkeysLoading,
    registeringPasskey,
    revokingPasskeyId,
    refreshPasskeys,
    registerPasskey,
    revokePasskey,
  };
};

export default usePasskeys;
