import { Hono } from "hono";
import { createFactory } from "hono/factory";
import { createClientMagicLink, consumeClientMagicLink, resolveClientMagicLink } from "../helpers/clientMagicLinks.js";
import {
  clientHasAnyActiveAccess,
  normalizeClientEmail,
} from "../helpers/clientAccounts.js";
import {
  buildClientSessionCookie,
  clearClientSessionCookie,
  signClientSession,
} from "../helpers/clientSessions.js";
import { MiddlewareManager } from "../middlewares/middleware.js";
import type { BaseEnv, ClientEnv } from "../types/app.types.js";

const publicFactory = createFactory<BaseEnv>();
const clientFactory = createFactory<ClientEnv>();

const isValidEmail = (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "");

const sendClientMagicLinkEmail = async (
  transporter: BaseEnv["Variables"]["transporter"],
  input: {
    email: string;
    subject: string;
    url: string;
  }
) => {
  await transporter.sendMail({
    from: "Xtablo <noreply@xtablo.com>",
    html: `
      <h2>${input.subject}</h2>
      <p>Bonjour,</p>
      <p>Utilisez le lien ci-dessous pour acceder a votre espace client :</p>
      <p><a href="${input.url}">Ouvrir mon espace client</a></p>
    `,
    subject: input.subject,
    to: input.email,
  });
};

const createClientSessionCookieHeader = (
  client: { email: string; id: string },
  config: Parameters<typeof signClientSession>[1] & {
    cookieDomain: string;
    cookieName: string;
  }
) => {
  const token = signClientSession(
    {
      clientId: client.id,
      email: client.email,
    },
    {
      expiresInDays: config.expiresInDays,
      secret: config.secret,
    }
  );

  return buildClientSessionCookie(token, {
    cookieDomain: config.cookieDomain,
    cookieName: config.cookieName,
    maxAgeSeconds: config.expiresInDays * 24 * 60 * 60,
  });
};

const requestLink = (config: {
  apiBaseUrl: string;
  clientsUrl: string;
  jwtSecret: string;
  ttlMinutes: number;
}) =>
  publicFactory.createHandlers(async (c) => {
    const supabase = c.get("supabase");
    const transporter = c.get("transporter");
    const body = await c.req.json().catch(() => ({}));
    const rawEmail = String((body as { email?: string }).email || "");
    const redirectToInput = String((body as { redirectTo?: string }).redirectTo || "/");
    const redirectTo = redirectToInput.startsWith("/") ? redirectToInput : "/";
    const normalizedEmail = normalizeClientEmail(rawEmail);

    if (!normalizedEmail || !isValidEmail(normalizedEmail)) {
      return c.json({ error: "A valid email is required" }, 400);
    }

    const { data: client } = await supabase
      .from("clients")
      .select("*")
      .eq("normalized_email", normalizedEmail)
      .maybeSingle();

    if (client) {
      const accessResult = await clientHasAnyActiveAccess(supabase, client.id);

      if (!accessResult.error && accessResult.hasActiveAccess) {
          const magicLinkResult = await createClientMagicLink(supabase, {
            clientId: client.id,
            email: client.email,
            expiresInMinutes: config.ttlMinutes,
            jwtSecret: config.jwtSecret,
            purpose: "login",
            redirectTo,
          });

        if (!magicLinkResult.error && magicLinkResult.token) {
          try {
            await sendClientMagicLinkEmail(transporter, {
              email: client.email,
              subject: "Votre lien de connexion Xtablo",
              url: `${trimTrailingSlash(config.clientsUrl)}/auth/exchange?token=${encodeURIComponent(
                magicLinkResult.token
              )}`,
            });
          } catch (emailError) {
            console.error("Failed to send client login email:", emailError);
          }
        }
      }
    }

    return c.json({
      success: true,
      message: "If this email can access the client portal, a connection link has been sent.",
    });
  });

const exchangeLink = (config: {
  clientsUrl: string;
  cookieDomain: string;
  cookieName: string;
  jwtSecret: string;
  sessionTtlDays: number;
}) =>
  publicFactory.createHandlers(async (c) => {
    const supabase = c.get("supabase");
    const token = c.req.query("token");

    if (!token) {
      return c.json({ error: "Magic link required" }, 400);
    }

    const resolution = await resolveClientMagicLink(supabase, {
      jwtSecret: config.jwtSecret,
      token,
    });

    if (resolution.status !== 200 || !resolution.link || !resolution.claims) {
      return c.json({ error: resolution.error ?? "Invalid magic link" }, resolution.status);
    }

    const consumeResult = await consumeClientMagicLink(supabase, resolution.link.id);
    if (!consumeResult.success) {
      return c.json({ error: consumeResult.error ?? "Failed to consume magic link" }, 500);
    }

    const { data: client, error: clientError } = await supabase
      .from("clients")
      .select("*")
      .eq("id", resolution.link.client_id)
      .single();

    if (clientError || !client) {
      return c.json({ error: clientError?.message ?? "Client not found" }, 404);
    }

    await supabase
      .from("clients")
      .update({ last_login_at: consumeResult.consumedAt })
      .eq("id", client.id);

    const cookieHeader = createClientSessionCookieHeader(
      { email: client.email, id: client.id },
      {
        cookieDomain: config.cookieDomain,
        cookieName: config.cookieName,
        expiresInDays: config.sessionTtlDays,
        secret: config.jwtSecret,
      }
    );

    c.header("Set-Cookie", cookieHeader);

    const redirectTo = resolution.link.redirect_to || resolution.claims.redirect_to || "/";
    return c.redirect(`${config.clientsUrl}${redirectTo}`);
  });

const logout = (config: { cookieDomain: string; cookieName: string }) =>
  publicFactory.createHandlers(async (c) => {
    c.header(
      "Set-Cookie",
      clearClientSessionCookie({
        cookieDomain: config.cookieDomain,
        cookieName: config.cookieName,
      })
    );

    return c.json({ success: true });
  });

const getCurrentClient = (middlewareManager: ReturnType<typeof MiddlewareManager.getInstance>) =>
  clientFactory.createHandlers(middlewareManager.clientAuth, async (c) => {
    return c.json({ client: c.get("client") });
  });

export const getClientAuthRouter = (config: {
  apiBaseUrl: string;
  clientsUrl: string;
  cookieDomain: string;
  cookieName: string;
  jwtSecret: string;
  magicLinkTtlMinutes: number;
  sessionTtlDays: number;
}) => {
  const router = new Hono<BaseEnv>();
  const middlewareManager = MiddlewareManager.getInstance();

  router.post(
    "/request-link",
    ...requestLink({
      apiBaseUrl: config.apiBaseUrl,
      clientsUrl: config.clientsUrl,
      jwtSecret: config.jwtSecret,
      ttlMinutes: config.magicLinkTtlMinutes,
    })
  );
  router.get(
    "/exchange",
    ...exchangeLink({
      clientsUrl: config.clientsUrl,
      cookieDomain: config.cookieDomain,
      cookieName: config.cookieName,
      jwtSecret: config.jwtSecret,
      sessionTtlDays: config.sessionTtlDays,
    })
  );
  router.post(
    "/logout",
    ...logout({
      cookieDomain: config.cookieDomain,
      cookieName: config.cookieName,
    })
  );
  router.get("/me", ...getCurrentClient(middlewareManager));

  return router;
};
