import { useState } from "react";
import { redirect } from "react-router";
import {
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
  type AuthenticationResponseJSON,
  type AuthenticatorTransportFuture,
} from "@simplewebauthn/server";
import type { Route } from "./+types/auth.login";
import { getSession, commitSession } from "~/lib/session.server";
import { listCredentials, getCredentialById, updateCredentialCounter } from "~/lib/db.server";
import { rpID, origin } from "~/lib/passkey.server";

export async function loader({ request }: Route.LoaderArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  if (session.get("authenticated")) return redirect("/");
  if (listCredentials().length === 0) return redirect("/auth/register");
  return {};
}

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 === "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 === "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 redirect("/", {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

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

export default function Login() {
  const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
  const [errorMsg, setErrorMsg] = useState("");

  async function handleLogin() {
    setStatus("loading");
    setErrorMsg("");
    try {
      const { startAuthentication } = await import("@simplewebauthn/browser");

      const optRes = await fetch("/auth/login?intent=options", { method: "POST" });
      if (!optRes.ok) throw new Error("オプション取得に失敗しました");
      const options = await optRes.json();

      const authResponse = await startAuthentication({ optionsJSON: options });

      const verRes = await fetch("/auth/login?intent=verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(authResponse),
      });
      if (!verRes.ok) {
        const err = await verRes.json();
        throw new Error(err.error ?? "認証に失敗しました");
      }

      window.location.href = "/";
    } catch (e) {
      setStatus("error");
      setErrorMsg(e instanceof Error ? e.message : "エラーが発生しました");
    }
  }

  return (
    <div className="wrap">
      <header className="site-header">
        <h1>log</h1>
      </header>
      <div className="auth-box">
        <h2>ログイン</h2>
        <p>登録済みのパスキーで認証します。</p>
        <button className="btn" onClick={handleLogin} disabled={status === "loading"}>
          {status === "loading" ? "認証中…" : "パスキーでログイン"}
        </button>
        {status === "error" && (
          <p className="error-msg" style={{ marginTop: ".75rem" }}>{errorMsg}</p>
        )}
      </div>
    </div>
  );
}
