import { useState } from "react";
import { redirect } from "react-router";
import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  type RegistrationResponseJSON,
  type AuthenticatorTransportFuture,
} from "@simplewebauthn/server";
import type { Route } from "./+types/auth.register";
import { getSession, commitSession } from "~/lib/session.server";
import { saveCredential } from "~/lib/db.server";
import { rpID, rpName, 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("/");
  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 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]), // "microblog"
      userName: "admin",
      userDisplayName: "admin",
      attestationType: "none",
      authenticatorSelection: {
        residentKey: "required",
        userVerification: "required",
      },
    });
    // 必須フィールドのみ送信（@simplewebauthn/browser を使わず直接 API を叩くため）
    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 === "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 redirect("/", {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

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

function b64urlToBuffer(b64url: string): ArrayBuffer {
  const base64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
  const pad = "=".repeat((4 - (base64.length % 4)) % 4);
  const binary = atob(base64 + pad);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
  return bytes.buffer;
}

function bufferToB64url(buf: ArrayBuffer): string {
  const bytes = new Uint8Array(buf);
  let binary = "";
  for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}

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

  async function handleRegister() {
    setStatus("loading");
    setErrorMsg("");
    try {
      const optRes = await fetch("/auth/register?intent=options", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ token }),
      });
      if (!optRes.ok) {
        const err = await optRes.json();
        throw new Error(err.error ?? "オプション取得に失敗しました");
      }
      const optJSON = await optRes.json();

      // @simplewebauthn/browser を使わず WebAuthn API を直接呼ぶ
      const credential = await navigator.credentials.create({
        publicKey: {
          rp: optJSON.rp,
          user: {
            id: b64urlToBuffer(optJSON.user.id),
            name: optJSON.user.name,
            displayName: optJSON.user.displayName ?? optJSON.user.name,
          },
          challenge: b64urlToBuffer(optJSON.challenge),
          pubKeyCredParams: optJSON.pubKeyCredParams,
          timeout: 60000,
          attestation: "none",
          authenticatorSelection: {
            residentKey: "required" as ResidentKeyRequirement,
            userVerification: "required" as UserVerificationRequirement,
          },
        },
      }) as PublicKeyCredential | null;

      if (!credential) throw new Error("クレデンシャルの作成に失敗しました");

      const attestation = credential.response as AuthenticatorAttestationResponse;
      const regResponse: RegistrationResponseJSON = {
        id: credential.id,
        rawId: bufferToB64url(credential.rawId),
        response: {
          clientDataJSON: bufferToB64url(attestation.clientDataJSON),
          attestationObject: bufferToB64url(attestation.attestationObject),
          transports: attestation.getTransports
            ? (attestation.getTransports() as AuthenticatorTransportFuture[])
            : [],
        },
        authenticatorAttachment: credential.authenticatorAttachment ?? undefined,
        clientExtensionResults: credential.getClientExtensionResults(),
        type: "public-key",
      };

      const verRes = await fetch("/auth/register?intent=verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(regResponse),
      });
      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.name}: ${e.message}` : String(e));
    }
  }

  return (
    <div className="wrap">
      <header className="site-header">
        <h1>log</h1>
      </header>
      <div className="auth-box">
        <h2>パスキーを登録</h2>
        <p>登録トークンを入力してパスキーを作成します。</p>
        <input
          type="password"
          placeholder="登録トークン"
          value={token}
          onChange={(e) => setToken(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && handleRegister()}
          style={{
            width: "100%",
            marginBottom: ".75rem",
            padding: ".5rem .75rem",
            border: "1px solid #d1d5db",
            borderRadius: "4px",
            fontSize: ".875rem",
            fontFamily: "ui-monospace, monospace",
          }}
        />
        <button className="btn" onClick={handleRegister} disabled={status === "loading" || !token}>
          {status === "loading" ? "登録中…" : "パスキーを登録"}
        </button>
        {status === "error" && (
          <p className="error-msg" style={{ marginTop: ".75rem" }}>{errorMsg}</p>
        )}
      </div>
    </div>
  );
}
