/**
 * مدیریت SSO — ارتباط با Identity Service و Keycloak.
 * توکن‌ها روی دامنهٔ پایه (.torbatyar.ir) در cookie ذخیره می‌شوند
 * تا بین apex و ساب‌دامین tenant مشترک باشند؛ روی localhost از sessionStorage.
 *
 * Access token توسط Keycloak صادر می‌شود؛ refresh_token نشست را تا سقف SSO تمدید می‌کند.
 */

const IDENTITY_API =
  typeof window !== "undefined"
    ? "/api/identity"
    : process.env.IDENTITY_SERVICE_URL ||
      process.env.NEXT_PUBLIC_IDENTITY_API_URL ||
      "http://localhost:8001";

const TOKEN_KEY = "superapp_access_token";
const REFRESH_KEY = "superapp_refresh_token";
const EXPIRES_AT_KEY = "superapp_access_expires_at";
const POST_LOGIN_REDIRECT_KEY = "superapp_post_login_redirect";
/** Cookie lifetime for access token storage (value is replaced on each refresh). */
const ACCESS_COOKIE_MAX_AGE = 60 * 60 * 24 * 14; // 14d storage; JWT exp still enforced
const REFRESH_COOKIE_MAX_AGE = 60 * 60 * 24 * 14; // 14d
/** Refresh ~2 minutes before JWT exp to avoid mid-request expiry. */
const REFRESH_SKEW_MS = 120_000;

export interface AuthConfig {
  issuer: string;
  client_id: string;
  authorization_endpoint: string;
  token_endpoint: string;
  logout_endpoint: string;
  callback_url: string;
}

export interface LoginUrlResponse {
  authorization_url: string;
  state: string;
}

export interface TokenBundle {
  access_token: string;
  refresh_token?: string;
  expires_in: number;
  token_type: string;
}

function cookieDomain(): string | null {
  if (typeof window === "undefined") return null;
  const host = window.location.hostname;
  if (host === "localhost" || host === "127.0.0.1") return null;
  const base = process.env.NEXT_PUBLIC_PLATFORM_BASE_DOMAIN || "torbatyar.ir";
  if (host === base || host.endsWith(`.${base}`)) return `.${base}`;
  return null;
}

function setCookie(name: string, value: string, maxAge: number): void {
  if (typeof document === "undefined") return;
  const domain = cookieDomain();
  const parts = [
    `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
    "Path=/",
    `Max-Age=${maxAge}`,
    "SameSite=Lax",
  ];
  if (domain) {
    parts.push(`Domain=${domain}`);
    if (window.location.protocol === "https:") parts.push("Secure");
  }
  document.cookie = parts.join("; ");
}

function getCookie(name: string): string | null {
  if (typeof document === "undefined") return null;
  const prefix = `${encodeURIComponent(name)}=`;
  for (const part of document.cookie.split(";")) {
    const item = part.trim();
    if (item.startsWith(prefix)) {
      return decodeURIComponent(item.slice(prefix.length));
    }
  }
  return null;
}

function deleteCookie(name: string): void {
  if (typeof document === "undefined") return;
  const domain = cookieDomain();
  const base = `${encodeURIComponent(name)}=; Path=/; Max-Age=0`;
  document.cookie = base;
  if (domain) {
    document.cookie = `${base}; Domain=${domain}`;
  }
}

function readJwtExpMs(token: string): number | null {
  try {
    const payload = token.split(".")[1];
    if (!payload) return null;
    const json = JSON.parse(
      atob(payload.replace(/-/g, "+").replace(/_/g, "/"))
    ) as { exp?: number };
    return typeof json.exp === "number" ? json.exp * 1000 : null;
  } catch {
    return null;
  }
}

export function getStoredToken(): string | null {
  if (typeof window === "undefined") return null;
  return getCookie(TOKEN_KEY) || sessionStorage.getItem(TOKEN_KEY);
}

export function getStoredRefreshToken(): string | null {
  if (typeof window === "undefined") return null;
  return getCookie(REFRESH_KEY) || sessionStorage.getItem(REFRESH_KEY);
}

export function storeTokens(tokens: TokenBundle): void {
  const jwtExp = readJwtExpMs(tokens.access_token);
  const expiresAt =
    jwtExp ?? Date.now() + Math.max(30, tokens.expires_in || 300) * 1000;
  sessionStorage.setItem(TOKEN_KEY, tokens.access_token);
  sessionStorage.setItem(EXPIRES_AT_KEY, String(expiresAt));
  // Cookie must outlive JWT so we can refresh after access token expires.
  setCookie(TOKEN_KEY, tokens.access_token, ACCESS_COOKIE_MAX_AGE);
  setCookie(EXPIRES_AT_KEY, String(expiresAt), ACCESS_COOKIE_MAX_AGE);
  if (tokens.refresh_token) {
    sessionStorage.setItem(REFRESH_KEY, tokens.refresh_token);
    setCookie(REFRESH_KEY, tokens.refresh_token, REFRESH_COOKIE_MAX_AGE);
  }
}

export function clearTokens(): void {
  sessionStorage.removeItem(TOKEN_KEY);
  sessionStorage.removeItem(REFRESH_KEY);
  sessionStorage.removeItem(EXPIRES_AT_KEY);
  deleteCookie(TOKEN_KEY);
  deleteCookie(REFRESH_KEY);
  deleteCookie(EXPIRES_AT_KEY);
}

let refreshInFlight: Promise<string | null> | null = null;

/** Prefer JWT `exp` claim — source of truth for Keycloak tokens. */
function accessExpiresAt(token?: string | null): number | null {
  const t = token ?? getStoredToken();
  const fromJwt = t ? readJwtExpMs(t) : null;
  if (fromJwt) return fromJwt;
  const raw = getCookie(EXPIRES_AT_KEY) || sessionStorage.getItem(EXPIRES_AT_KEY);
  if (raw) {
    const n = Number(raw);
    if (!Number.isNaN(n)) return n;
  }
  return null;
}

function isExpired(token: string | null, skewMs = 0): boolean {
  if (!token) return true;
  const exp = accessExpiresAt(token);
  if (!exp) return false;
  return Date.now() >= exp - skewMs;
}

function needsRefresh(token: string | null): boolean {
  return isExpired(token, REFRESH_SKEW_MS);
}

/** Refresh access token via Identity BFF. Returns new access token or null. */
export async function refreshSession(options?: { force?: boolean }): Promise<string | null> {
  const refresh = getStoredRefreshToken();
  if (!refresh) return null;
  if (!options?.force) {
    const current = getStoredToken();
    if (current && !needsRefresh(current)) return current;
  }
  if (refreshInFlight) return refreshInFlight;

  refreshInFlight = (async () => {
    try {
      const res = await fetch(`${IDENTITY_API}/api/v1/auth/refresh`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refresh_token: refresh }),
      });
      if (!res.ok) {
        // Refresh token itself expired / revoked — clear so UI can re-login cleanly.
        if (res.status === 401 || res.status === 400) {
          clearTokens();
        }
        return null;
      }
      const bundle = (await res.json()) as TokenBundle;
      storeTokens(bundle);
      return bundle.access_token;
    } catch {
      return null;
    } finally {
      refreshInFlight = null;
    }
  })();

  return refreshInFlight;
}

/**
 * Return a usable access token, refreshing when near/after expiry.
 * Never returns a JWT that is already past exp.
 */
export async function getValidAccessToken(): Promise<string | null> {
  const token = getStoredToken();
  if (token && !needsRefresh(token)) return token;

  if (getStoredRefreshToken()) {
    const refreshed = await refreshSession({ force: isExpired(token) });
    if (refreshed && !isExpired(refreshed)) return refreshed;
  }

  // Last resort: non-expired access without refresh token
  if (token && !isExpired(token)) return token;
  return null;
}

export async function fetchAuthConfig(): Promise<AuthConfig> {
  let res: Response;
  try {
    res = await fetch(`${IDENTITY_API}/api/v1/auth/config`);
  } catch {
    throw new Error(
      `اتصال به سرویس احراز هویت برقرار نشد (${IDENTITY_API}). مطمئن شوید identity-access-service در حال اجراست.`
    );
  }
  if (!res.ok) throw new Error("دریافت پیکربندی SSO ناموفق بود");
  return res.json();
}

/** ذخیره مسیر/URL بازگشت بعد از لاگین (cookie مشترک بین ساب‌دامین‌ها). */
export function setPostLoginRedirect(pathOrUrl: string): void {
  if (typeof window === "undefined") return;
  sessionStorage.setItem(POST_LOGIN_REDIRECT_KEY, pathOrUrl);
  setCookie(POST_LOGIN_REDIRECT_KEY, pathOrUrl, 60 * 30);
}

export function consumePostLoginRedirect(fallback = "/dashboard"): string {
  if (typeof window === "undefined") return fallback;
  const fromSession = sessionStorage.getItem(POST_LOGIN_REDIRECT_KEY);
  const fromCookie = getCookie(POST_LOGIN_REDIRECT_KEY);
  sessionStorage.removeItem(POST_LOGIN_REDIRECT_KEY);
  deleteCookie(POST_LOGIN_REDIRECT_KEY);
  return fromSession || fromCookie || fallback;
}

export async function loginWithRedirect(redirectPath?: string): Promise<void> {
  const returnTo =
    redirectPath ||
    (typeof window !== "undefined" ? window.location.href : "/dashboard");
  setPostLoginRedirect(returnTo);
  const base = process.env.NEXT_PUBLIC_PLATFORM_BASE_DOMAIN || "torbatyar.ir";
  const protocol = typeof window !== "undefined" ? window.location.protocol : "https:";
  const apex = `${protocol}//${base}`;
  const host = typeof window !== "undefined" ? window.location.hostname : "";
  if (host && host !== base && host !== `www.${base}` && !host.includes("localhost")) {
    window.location.href = `${apex}/login?redirect=${encodeURIComponent(returnTo)}`;
    return;
  }
  const qs = `?redirect=${encodeURIComponent(returnTo)}`;
  window.location.href = `/login${qs}`;
}

export async function fetchLoginUrl(): Promise<string> {
  let res: Response;
  try {
    res = await fetch(`${IDENTITY_API}/api/v1/auth/login-url`);
  } catch {
    throw new Error(
      `اتصال به سرویس احراز هویت برقرار نشد (${IDENTITY_API}). مطمئن شوید identity-access-service در حال اجراست.`
    );
  }
  if (!res.ok) throw new Error("ساخت آدرس ورود ناموفق بود");
  const data: LoginUrlResponse = await res.json();
  return data.authorization_url;
}

export async function redeemHandoff(handoffCode: string): Promise<TokenBundle> {
  const res = await fetch(`${IDENTITY_API}/api/v1/auth/session/redeem`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ handoff_code: handoffCode }),
  });
  if (!res.ok) {
    let detail = "";
    try {
      const body = await res.json();
      detail = body?.error?.message || body?.detail || "";
    } catch {
      /* ignore */
    }
    throw new Error(detail || "تبدیل session ناموفق بود");
  }
  return res.json();
}

export async function exchangeCode(
  code: string,
  redirectUri: string,
  state: string | null
): Promise<TokenBundle> {
  const res = await fetch(`${IDENTITY_API}/api/v1/auth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      code,
      redirect_uri: redirectUri,
      state,
    }),
  });
  if (!res.ok) {
    let detail = "";
    try {
      const body = await res.json();
      detail = body?.error?.message || body?.detail || "";
    } catch {
      /* ignore */
    }
    throw new Error(
      detail ? `تبدیل code به token ناموفق بود: ${detail}` : "تبدیل code به token ناموفق بود"
    );
  }
  return res.json();
}

export async function fetchMe(token: string) {
  const res = await fetch(`${IDENTITY_API}/api/v1/auth/me`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error("دریافت اطلاعات کاربر ناموفق بود");
  return res.json() as Promise<{
    user_id: string;
    email: string | null;
    username: string | null;
    mobile: string | null;
    mobile_verified: boolean;
    requires_mobile_verification: boolean;
    roles: string[];
  }>;
}

export async function logout(config: AuthConfig): Promise<void> {
  clearTokens();
  const base = process.env.NEXT_PUBLIC_PLATFORM_BASE_DOMAIN || "torbatyar.ir";
  const protocol = window.location.protocol;
  const postLogout =
    window.location.hostname.endsWith(base) || window.location.hostname === base
      ? `${protocol}//${base}`
      : window.location.origin;
  const params = new URLSearchParams({
    client_id: config.client_id,
    post_logout_redirect_uri: postLogout,
  });
  window.location.href = `${config.logout_endpoint}?${params.toString()}`;
}
