import { randomBytes } from "node:crypto";
import type { Express, Request, Response } from "express";
import { parse as parseCookieHeader } from "cookie";
import { createSessionToken, loginOAuthUser, setSessionCookie } from "./_core/auth";
import { getSessionCookieOptions } from "./_core/cookies";
import { ENV } from "./_core/env";

const GOOGLE_STATE_COOKIE = "oauth_state_google";
const FACEBOOK_STATE_COOKIE = "oauth_state_facebook";
const OAUTH_STATE_MAX_AGE_MS = 10 * 60 * 1000;

function parseCookies(cookieHeader: string | undefined) {
  if (!cookieHeader) return new Map<string, string>();
  return new Map(Object.entries(parseCookieHeader(cookieHeader)));
}

function buildBaseUrl(req: Request) {
  if (ENV.appBaseUrl) {
    return ENV.appBaseUrl.replace(/\/$/, "");
  }

  const forwardedProto = req.headers["x-forwarded-proto"];
  const forwardedHost = req.headers["x-forwarded-host"];
  const proto = typeof forwardedProto === "string"
    ? forwardedProto.split(",")[0]?.trim()
    : req.protocol;
  const host = typeof forwardedHost === "string"
    ? forwardedHost.split(",")[0]?.trim()
    : req.get("host");

  return `${proto || "http"}://${host}`;
}

function getCallbackUrl(req: Request, provider: "google" | "facebook") {
  return `${buildBaseUrl(req)}/auth/${provider}/callback`;
}

function redirectToLogin(res: Response, message: string) {
  res.redirect(`/login?authError=${encodeURIComponent(message)}`);
}

function setOAuthStateCookie(req: Request, res: Response, name: string, value: string) {
  res.cookie(name, value, {
    ...getSessionCookieOptions(req),
    maxAge: OAUTH_STATE_MAX_AGE_MS,
  });
}

function clearOAuthStateCookie(req: Request, res: Response, name: string) {
  res.clearCookie(name, {
    ...getSessionCookieOptions(req),
    maxAge: -1,
  });
}

function readAndValidateState(req: Request, res: Response, cookieName: string, receivedState: string | null) {
  const cookies = parseCookies(req.headers.cookie);
  const expectedState = cookies.get(cookieName) ?? "";
  clearOAuthStateCookie(req, res, cookieName);
  return Boolean(receivedState && expectedState && receivedState === expectedState);
}

function createOAuthState() {
  return randomBytes(24).toString("hex");
}

function isGoogleConfigured() {
  return Boolean(ENV.googleClientId && ENV.googleClientSecret);
}

function isFacebookConfigured() {
  return Boolean(ENV.facebookAppId && ENV.facebookAppSecret);
}

async function finishLogin(req: Request, res: Response, userData: {
  provider: "google" | "facebook";
  providerUserId: string;
  email: string;
  name?: string | null;
}) {
  const user = await loginOAuthUser(userData);
  const token = await createSessionToken(user);
  setSessionCookie(req, res, token);
  res.redirect(user.role === "service_terrain" ? "/terrain" : "/dashboard");
}

export function getOAuthProviderStatus() {
  return {
    google: isGoogleConfigured(),
    facebook: isFacebookConfigured(),
  };
}

export function registerSocialAuthRoutes(app: Express) {
  app.get("/auth/google/start", (req, res) => {
    if (!isGoogleConfigured()) {
      redirectToLogin(res, "Connexion Google non configurée");
      return;
    }

    const state = createOAuthState();
    setOAuthStateCookie(req, res, GOOGLE_STATE_COOKIE, state);

    const params = new URLSearchParams({
      client_id: ENV.googleClientId,
      redirect_uri: getCallbackUrl(req, "google"),
      response_type: "code",
      scope: "openid email profile",
      state,
      access_type: "online",
      include_granted_scopes: "true",
      prompt: "select_account",
    });

    res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);
  });

  app.get("/auth/google/callback", async (req, res) => {
    try {
      if (!isGoogleConfigured()) {
        redirectToLogin(res, "Connexion Google non configurée");
        return;
      }

      const code = typeof req.query.code === "string" ? req.query.code : "";
      const state = typeof req.query.state === "string" ? req.query.state : null;

      if (!code || !readAndValidateState(req, res, GOOGLE_STATE_COOKIE, state)) {
        redirectToLogin(res, "Connexion Google invalide ou expirée");
        return;
      }

      const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
        method: "POST",
        headers: {
          "content-type": "application/x-www-form-urlencoded",
        },
        body: new URLSearchParams({
          code,
          client_id: ENV.googleClientId,
          client_secret: ENV.googleClientSecret,
          redirect_uri: getCallbackUrl(req, "google"),
          grant_type: "authorization_code",
        }),
      });

      if (!tokenResponse.ok) {
        redirectToLogin(res, "Google a refusé la connexion");
        return;
      }

      const tokenData = await tokenResponse.json() as { access_token?: string };
      if (!tokenData.access_token) {
        redirectToLogin(res, "Jeton Google manquant");
        return;
      }

      const userInfoResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
        headers: {
          authorization: `Bearer ${tokenData.access_token}`,
        },
      });

      if (!userInfoResponse.ok) {
        redirectToLogin(res, "Impossible de récupérer le profil Google");
        return;
      }

      const userInfo = await userInfoResponse.json() as {
        sub?: string;
        email?: string;
        email_verified?: boolean;
        name?: string;
      };

      if (!userInfo.sub || !userInfo.email || userInfo.email_verified === false) {
        redirectToLogin(res, "Le compte Google doit fournir une adresse email vérifiée");
        return;
      }

      await finishLogin(req, res, {
        provider: "google",
        providerUserId: userInfo.sub,
        email: userInfo.email,
        name: userInfo.name,
      });
    } catch (error) {
      console.error("[OAuth] Google callback failed:", error);
      redirectToLogin(res, "Connexion Google impossible pour le moment");
    }
  });

  app.get("/auth/facebook/start", (req, res) => {
    if (!isFacebookConfigured()) {
      redirectToLogin(res, "Connexion Facebook non configurée");
      return;
    }

    const state = createOAuthState();
    setOAuthStateCookie(req, res, FACEBOOK_STATE_COOKIE, state);

    const params = new URLSearchParams({
      client_id: ENV.facebookAppId,
      redirect_uri: getCallbackUrl(req, "facebook"),
      state,
      scope: "email,public_profile",
    });

    res.redirect(`https://www.facebook.com/dialog/oauth?${params.toString()}`);
  });

  app.get("/auth/facebook/callback", async (req, res) => {
    try {
      if (!isFacebookConfigured()) {
        redirectToLogin(res, "Connexion Facebook non configurée");
        return;
      }

      const code = typeof req.query.code === "string" ? req.query.code : "";
      const state = typeof req.query.state === "string" ? req.query.state : null;

      if (!code || !readAndValidateState(req, res, FACEBOOK_STATE_COOKIE, state)) {
        redirectToLogin(res, "Connexion Facebook invalide ou expirée");
        return;
      }

      const tokenUrl = new URL("https://graph.facebook.com/oauth/access_token");
      tokenUrl.search = new URLSearchParams({
        client_id: ENV.facebookAppId,
        client_secret: ENV.facebookAppSecret,
        redirect_uri: getCallbackUrl(req, "facebook"),
        code,
      }).toString();

      const tokenResponse = await fetch(tokenUrl);
      if (!tokenResponse.ok) {
        redirectToLogin(res, "Facebook a refusé la connexion");
        return;
      }

      const tokenData = await tokenResponse.json() as { access_token?: string };
      if (!tokenData.access_token) {
        redirectToLogin(res, "Jeton Facebook manquant");
        return;
      }

      const profileUrl = new URL("https://graph.facebook.com/me");
      profileUrl.search = new URLSearchParams({
        fields: "id,name,email",
        access_token: tokenData.access_token,
      }).toString();

      const profileResponse = await fetch(profileUrl);
      if (!profileResponse.ok) {
        redirectToLogin(res, "Impossible de récupérer le profil Facebook");
        return;
      }

      const profile = await profileResponse.json() as {
        id?: string;
        name?: string;
        email?: string;
      };

      if (!profile.id || !profile.email) {
        redirectToLogin(res, "Le compte Facebook doit partager une adresse email");
        return;
      }

      await finishLogin(req, res, {
        provider: "facebook",
        providerUserId: profile.id,
        email: profile.email,
        name: profile.name,
      });
    } catch (error) {
      console.error("[OAuth] Facebook callback failed:", error);
      redirectToLogin(res, "Connexion Facebook impossible pour le moment");
    }
  });
}
