import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
  type RegistrationResponseJSON,
  type AuthenticationResponseJSON,
  type AuthenticatorTransportFuture,
} from "@simplewebauthn/server";
import type { Route } from "./+types/api.passkey";
import { getSession, commitSession } from "~/lib/session.server";
import {
  saveCredential,
  listCredentials,
  getCredentialById,
  updateCredentialCounter,
} from "~/lib/db.server";
import { rpID, rpName, origin } from "~/lib/passkey.server";

export async function action({ request }: Route.ActionArgs) {
  const url = new URL(request.url);
  const intent = url.searchParams.get("intent");
  const session = await getSession(request.headers.get("Cookie"));

  if (intent === "reg-options") {
    const body = (await request.json()) as { token?: string };
    const registerToken = process.env.REGISTER_TOKEN;
    if (!registerToken || body.token !== registerToken) {
      return Response.json({ error: "トークンが違います" }, { status: 403 });
    }
    const options = await generateRegistrationOptions({
      rpName,
      rpID,
      userID: new Uint8Array([109, 105, 99, 114, 111, 98, 108, 111, 103]),
      userName: "admin",
      userDisplayName: "admin",
      attestationType: "none",
      authenticatorSelection: { residentKey: "required", userVerification: "required" },
    });
    const minimalOptions = {
      rp: options.rp,
      user: options.user,
      challenge: options.challenge,
      pubKeyCredParams: options.pubKeyCredParams.filter((p) => p.alg === -7 || p.alg === -257),
    };
    session.set("challenge", options.challenge);
    return Response.json(minimalOptions, {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

  if (intent === "reg-verify") {
    const challenge = session.get("challenge") as string | undefined;
    if (!challenge) return Response.json({ error: "セッション切れです" }, { status: 400 });
    const body = (await request.json()) as RegistrationResponseJSON;
    let verification;
    try {
      verification = await verifyRegistrationResponse({
        response: body,
        expectedChallenge: challenge,
        expectedOrigin: origin,
        expectedRPID: rpID,
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      console.error("[passkey] verifyRegistrationResponse failed:", msg);
      return Response.json({ error: msg }, { status: 400 });
    }
    if (!verification.verified || !verification.registrationInfo) {
      return Response.json({ error: "登録に失敗しました" }, { status: 400 });
    }
    const { credential } = verification.registrationInfo;
    saveCredential({
      id: credential.id,
      publicKey: credential.publicKey,
      counter: credential.counter,
      transports: (body.response as { transports?: AuthenticatorTransportFuture[] }).transports,
    });
    session.unset("challenge");
    session.set("authenticated", true);
    return Response.json({ ok: true }, {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

  if (intent === "login-options") {
    const credentials = listCredentials();
    const options = await generateAuthenticationOptions({
      rpID,
      allowCredentials: credentials.map((c) => ({
        id: c.id,
        transports: c.transports
          ? (JSON.parse(c.transports) as AuthenticatorTransportFuture[])
          : undefined,
      })),
      userVerification: "required",
    });
    session.set("challenge", options.challenge);
    return Response.json(options, {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

  if (intent === "login-verify") {
    const challenge = session.get("challenge") as string | undefined;
    if (!challenge) return Response.json({ error: "セッション切れです" }, { status: 400 });
    const body = (await request.json()) as AuthenticationResponseJSON;
    const credential = getCredentialById(body.id);
    if (!credential) return Response.json({ error: "パスキーが見つかりません" }, { status: 400 });
    const verification = await verifyAuthenticationResponse({
      response: body,
      expectedChallenge: challenge,
      expectedOrigin: origin,
      expectedRPID: rpID,
      credential: {
        id: credential.id,
        publicKey: new Uint8Array(Buffer.from(credential.public_key, "base64url")),
        counter: credential.counter,
        transports: credential.transports
          ? (JSON.parse(credential.transports) as AuthenticatorTransportFuture[])
          : undefined,
      },
    });
    if (!verification.verified) {
      return Response.json({ error: "認証に失敗しました" }, { status: 400 });
    }
    updateCredentialCounter(credential.id, verification.authenticationInfo.newCounter);
    session.unset("challenge");
    session.set("authenticated", true);
    return Response.json({ ok: true }, {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

  return Response.json({ error: "不正なリクエスト" }, { status: 400 });
}
