import { useEffect, useRef, useState } from "react";
import { Form, redirect, useNavigation } from "react-router";
import type { Route } from "./+types/home";
import { createPost, deletePost, listPosts, listCredentials } from "~/lib/db.server";
import { renderMarkdown } from "~/lib/md.server";
import { getSession, commitSession } from "~/lib/session.server";

export async function loader({ request }: Route.LoaderArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  const isAuthenticated = session.get("authenticated") === true;
  const hasCredentials = listCredentials().length > 0;
  const posts = listPosts();
  return {
    posts: posts.map((p) => ({ ...p, html: renderMarkdown(p.content) })),
    isAuthenticated,
    hasCredentials,
  };
}

export async function action({ request }: Route.ActionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  if (session.get("authenticated") !== true) {
    throw new Response("Unauthorized", { status: 401 });
  }

  const form = await request.formData();
  const intent = String(form.get("intent") ?? "create");

  if (intent === "delete") {
    const id = String(form.get("id") ?? "");
    if (id) deletePost(id);
    return redirect("/", {
      headers: { "Set-Cookie": await commitSession(session) },
    });
  }

  const content = String(form.get("content") ?? "").trim();
  if (!content) return { error: "内容を入力してください", content };
  if (content.length > 10000) return { error: "投稿が長すぎます（最大10,000文字）", content };

  createPost({ id: crypto.randomUUID(), content });
  return redirect("/", {
    headers: { "Set-Cookie": await commitSession(session) },
  });
}

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, "");
}

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;
}

export default function Home({ loaderData, actionData }: Route.ComponentProps) {
  const { posts, isAuthenticated, hasCredentials } = loaderData;
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";
  const formRef = useRef<HTMLFormElement>(null);
  const wasSubmitting = useRef(false);
  const [loginStatus, setLoginStatus] = useState<"idle" | "loading" | "error">("idle");
  const [loginError, setLoginError] = useState("");

  async function handleLogin() {
    setLoginStatus("loading");
    setLoginError("");
    try {
      const optRes = await fetch("/api/passkey?intent=login-options", { method: "POST" });
      if (!optRes.ok) throw new Error("オプション取得に失敗しました");
      const options = await optRes.json();

      const credential = await navigator.credentials.get({
        publicKey: {
          challenge: b64urlToBuffer(options.challenge as string),
          rpId: options.rpId as string,
          allowCredentials: (options.allowCredentials as Array<{ id: string; transports?: string[] }> ?? []).map((c) => ({
            id: b64urlToBuffer(c.id),
            type: "public-key" as const,
            transports: c.transports as AuthenticatorTransport[] | undefined,
          })),
          userVerification: "required" as UserVerificationRequirement,
          timeout: 60000,
        },
      }) as PublicKeyCredential | null;

      if (!credential) throw new Error("認証に失敗しました");

      const assertion = credential.response as AuthenticatorAssertionResponse;
      const verRes = await fetch("/api/passkey?intent=login-verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          id: credential.id,
          rawId: bufferToB64url(credential.rawId),
          response: {
            clientDataJSON: bufferToB64url(assertion.clientDataJSON),
            authenticatorData: bufferToB64url(assertion.authenticatorData),
            signature: bufferToB64url(assertion.signature),
            userHandle: assertion.userHandle ? bufferToB64url(assertion.userHandle) : undefined,
          },
          authenticatorAttachment: credential.authenticatorAttachment ?? undefined,
          clientExtensionResults: credential.getClientExtensionResults(),
          type: "public-key",
        }),
      });
      if (!verRes.ok) {
        const err = await verRes.json();
        throw new Error(err.error ?? "認証に失敗しました");
      }

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

  useEffect(() => {
    if (navigation.state === "submitting") wasSubmitting.current = true;
    if (wasSubmitting.current && navigation.state === "idle" && !actionData?.error) {
      formRef.current?.reset();
      wasSubmitting.current = false;
    }
  }, [navigation.state, actionData?.error]);

  return (
    <div className="wrap">
      <header className="site-header">
        <h1>log</h1>
        <nav className="header-nav">
          {isAuthenticated ? (
            <Form method="post" action="/auth/logout">
              <button type="submit" className="logout-btn">ログアウト</button>
            </Form>
          ) : hasCredentials ? (
            <>
              <button className="auth-link" onClick={handleLogin} disabled={loginStatus === "loading"}>
                {loginStatus === "loading" ? "認証中…" : "ログイン"}
              </button>
              {loginStatus === "error" && (
                <span className="error-msg" style={{ marginLeft: ".5rem", fontSize: ".85em" }}>{loginError}</span>
              )}
            </>
          ) : (
            <a href="/auth/register" className="auth-link">登録</a>
          )}
        </nav>
      </header>

      {isAuthenticated && (
        <div className="compose">
          <Form method="post" ref={formRef}>
            <input type="hidden" name="intent" value="create" />
            <textarea
              name="content"
              placeholder="Markdown で投稿..."
              rows={4}
              defaultValue={actionData?.content ?? ""}
              autoFocus
            />
            {actionData?.error && (
              <p className="error-msg">{actionData.error}</p>
            )}
            <div className="compose-row">
              <span className="hint">markdown</span>
              <button type="submit" disabled={isSubmitting}>
                {isSubmitting ? "投稿中…" : "投稿"}
              </button>
            </div>
          </Form>
        </div>
      )}

      <div className="feed">
        {posts.length === 0 && (
          <p className="empty">まだ投稿がありません</p>
        )}
        {posts.map((post) => (
          <article key={post.id} className="post">
            <div className="post-header">
              <time className="post-meta" dateTime={post.created_at + "Z"}>
                {formatDate(post.created_at)}
              </time>
              {isAuthenticated && (
                <Form method="post">
                  <input type="hidden" name="intent" value="delete" />
                  <input type="hidden" name="id" value={post.id} />
                  <button
                    type="submit"
                    className="delete-btn"
                    onClick={(e) => {
                      if (!confirm("この投稿を削除しますか？")) e.preventDefault();
                    }}
                  >
                    削除
                  </button>
                </Form>
              )}
            </div>
            <div
              className="prose"
              dangerouslySetInnerHTML={{ __html: post.html }}
            />
          </article>
        ))}
      </div>
    </div>
  );
}

function formatDate(isoUtc: string): string {
  return new Date(isoUtc + "Z").toLocaleString("ja-JP", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    timeZone: "Asia/Tokyo",
  });
}
