import type { SupabaseClient } from "@supabase/supabase-js";
import { generateToken } from "./token.js";
import {
  hashClientMagicLinkToken,
  signClientMagicLink,
  verifyClientMagicLink,
  type MagicLinkPurpose,
} from "./clientSessions.js";

type CreateClientMagicLinkInput = {
  clientId: string;
  createdBy?: string | null;
  email: string;
  expiresInMinutes: number;
  jwtSecret: string;
  purpose: MagicLinkPurpose;
  redirectTo?: string;
  tabloId?: string | null;
};

export async function createClientMagicLink(
  supabase: SupabaseClient,
  input: CreateClientMagicLinkInput
) {
  const jti = generateToken();
  const token = signClientMagicLink(
    {
      clientId: input.clientId,
      email: input.email,
      jti,
      purpose: input.purpose,
      redirectTo: input.redirectTo,
    },
    {
      expiresInMinutes: input.expiresInMinutes,
      secret: input.jwtSecret,
    }
  );

  const expiresAt = new Date(Date.now() + input.expiresInMinutes * 60 * 1000).toISOString();
  const tokenHash = hashClientMagicLinkToken(token);

  const { data, error } = await supabase
    .from("client_magic_links")
    .insert({
      client_id: input.clientId,
      created_by: input.createdBy ?? null,
      email: input.email,
      expires_at: expiresAt,
      jti,
      purpose: input.purpose,
      redirect_to: input.redirectTo ?? null,
      tablo_id: input.tabloId ?? null,
      token_hash: tokenHash,
    })
    .select("*")
    .single();

  if (error) {
    return { error: error.message, link: null, token: null };
  }

  return {
    error: null,
    link: data,
    token,
  };
}

export async function resolveClientMagicLink(
  supabase: SupabaseClient,
  input: {
    expectedPurpose?: MagicLinkPurpose;
    jwtSecret: string;
    token: string;
  }
) {
  try {
    const claims = verifyClientMagicLink(input.token, {
      secret: input.jwtSecret,
    });

    if (input.expectedPurpose && claims.purpose !== input.expectedPurpose) {
      return { error: "Magic link purpose mismatch", link: null, status: 404 as const };
    }

    const { data: link, error } = await supabase
      .from("client_magic_links")
      .select("*")
      .eq("jti", claims.jti)
      .maybeSingle();

    if (error) {
      return { error: error.message, link: null, status: 500 as const };
    }

    if (!link) {
      return { error: "Magic link not found", link: null, status: 404 as const };
    }

    if (link.token_hash && link.token_hash !== hashClientMagicLinkToken(input.token)) {
      return { error: "Magic link not found", link: null, status: 404 as const };
    }

    if (link.consumed_at) {
      return { error: "Magic link already used", link: null, status: 404 as const };
    }

    if (new Date(link.expires_at).getTime() < Date.now()) {
      return { error: "Magic link expired", link: null, status: 410 as const };
    }

    return { claims, error: null, link, status: 200 as const };
  } catch (error) {
    const message = error instanceof Error ? error.message : "Invalid magic link";
    const status = /expired/i.test(message) ? (410 as const) : (404 as const);
    return { error: message, link: null, status };
  }
}

export async function consumeClientMagicLink(supabase: SupabaseClient, linkId: number) {
  const consumedAt = new Date().toISOString();
  const { error } = await supabase
    .from("client_magic_links")
    .update({ consumed_at: consumedAt })
    .eq("id", linkId)
    .is("consumed_at", null);

  return { consumedAt, error: error?.message ?? null, success: !error };
}
