import { computed, nextTick } from "vue";
import { navigateTo, useCookie, useRequestHeaders, useRuntimeConfig, useState } from "#app";
import { $fetch } from "ofetch";
import { userFacingApiError } from "~/utils/workpulse-api-error";
import { normalizeWorkpulseApiBase } from "~/utils/workpulse-api-base";
import {
  clearWorkpulseSessionUser,
  persistWorkpulseSessionTokens,
  persistWorkpulseSessionUser,
  readWorkpulseAccessToken,
  readWorkpulseRefreshToken,
  readRememberMePreference,
  setRememberMePreference
} from "~/utils/workpulse-session-user";

const STORAGE_KEY = "workpulse-auth";
const SESSION_COOKIE_MAX_AGE = 60 * 60 * 24 * 14;

/** Cookie `workpulse-auth` harus ikut skema asli user: HTTPS + `Secure` (di balik Apache: `X-Forwarded-Proto`). */
function workpulseSessionCookieSecure(): boolean {
  if (import.meta.client && typeof globalThis.location !== "undefined") {
    return globalThis.location.protocol === "https:";
  }
  try {
    const h = useRequestHeaders(["x-forwarded-proto"]);
    const raw = h["x-forwarded-proto"];
    const first = (Array.isArray(raw) ? raw[0] : raw)?.split(",")[0]?.trim();
    return first === "https";
  } catch {
    return false;
  }
}

/**
 * Nuxt `useCookie` menulis `document.cookie` lewat `watch` (bisa tertunda ke microtask berikutnya).
 * Setelah login, `navigateTo("/")` memicu fetch payload ke Nitro; tanpa cookie di dokumen, middleware
 * `00-workpulse-auth-redirect` mengira belum auth → 302 `/login` → user “tidak pernah masuk”.
 */
function persistWorkpulseAuthFlagClient(value: "0" | "1") {
  if (!import.meta.client) return;
  const secure = workpulseSessionCookieSecure();
  const parts = [
    `${STORAGE_KEY}=${encodeURIComponent(value)}`,
    "Path=/",
    `Max-Age=${SESSION_COOKIE_MAX_AGE}`,
    "SameSite=Lax"
  ];
  if (secure) parts.push("Secure");
  document.cookie = parts.join("; ");
}

type ApiEnvelope<T> = {
  ok: boolean;
  data?: T;
  error?: { code?: string; message?: string };
};

type LoginData = {
  accessToken: string;
  refreshToken: string;
  user: {
    id: number;
    email: string;
    name: string;
    role: string;
    permissions?: Record<string, boolean>;
  };
};

export type LoginResult = { ok: true } | { ok: false; message: string };

export const useAuth = () => {
  const authCookie = useCookie<string>(STORAGE_KEY, {
    default: () => "0",
    path: "/",
    sameSite: "lax",
    secure: workpulseSessionCookieSecure(),
    /** Supaya flag sesi tidak hilang saat navigasi client / refresh (middleware membaca cookie ini). */
    maxAge: SESSION_COOKIE_MAX_AGE,
    /**
     * Tanpa decode kustom, default Nuxt memakai `destr` → nilai cookie `"1"` jadi **angka** `1`.
     * `authCookie.value === "1"` lalu selalu false → middleware mengirim balik ke `/login` (seolah logout otomatis).
     */
    decode: (val) => {
      try {
        return decodeURIComponent(String(val ?? ""));
      } catch {
        return String(val ?? "");
      }
    }
  });

  const isAuthenticated = computed(() => {
    const v = authCookie.value as unknown;
    return v === "1" || v === 1 || v === true;
  });

  const accessToken = useState<string | null>("workpulse:access-token", () => null);
  const refreshToken = useState<string | null>("workpulse:refresh-token", () => null);

  const setAuth = (value: boolean) => {
    const v = value ? "1" : "0";
    authCookie.value = v;
    persistWorkpulseAuthFlagClient(v as "0" | "1");
  };

  const initTokensFromSession = () => {
    if (!import.meta.client) return;
    const at = readWorkpulseAccessToken();
    const rt = readWorkpulseRefreshToken();
    if (at) accessToken.value = at;
    if (rt) refreshToken.value = rt;
  };

  const initAuth = () => {
    if (!import.meta.client) return;
    try {
      if (localStorage.getItem(STORAGE_KEY) === "1") {
        setAuth(true);
        localStorage.removeItem(STORAGE_KEY);
      }
    } catch {
      /* ignore */
    }
  };

  /** Login demo lokal (tanpa API) — hindari di produksi. */
  const signIn = () => setAuth(true);
  const signOut = () => setAuth(false);

  const setAccessToken = (token: string | null) => {
    accessToken.value = token;
  };

  const setRefreshToken = (token: string | null) => {
    refreshToken.value = token;
  };

  const apiBase = () =>
    normalizeWorkpulseApiBase(String(useRuntimeConfig().public.workpulseApiBase || ""));

  const msgAuthTimeoutBe =
    "Permintaan melebihi batas waktu di server (database atau proksi). Coba lagi — lihat docs/BE_OPS_LOGIN_API_HANG.md.";

  /**
   * Login ke Gin: POST /api/v1/auth/login { email, password }
   * Respons: { ok, data: { accessToken, refreshToken, user }, error }
   * BE: 401 + invalid_credentials = kredensial salah; 504 + error.code "timeout" = timeout DB/proksi (bukan password salah).
   */
  const loginWithPassword = async (
    email: string,
    password: string,
    rememberMe = false
  ): Promise<LoginResult> => {
    const base = apiBase();
    if (!base) {
      return {
        ok: false,
        message: "API belum dikonfigurasi. Set NUXT_PUBLIC_WORKPULSE_API_BASE (lihat nuxt.config / PM2)."
      };
    }
    const ac = new AbortController();
    const timer = setTimeout(() => ac.abort(), 22_000);
    try {
      const raw = await $fetch.raw(`${base}/api/v1/auth/login`, {
        method: "POST",
        body: { email: email.trim(), password },
        credentials: "include",
        /** Supaya 401/403/504 tetap dapat body `{ ok, error }` tanpa lempar FetchError */
        ignoreResponseError: true,
        signal: ac.signal
      });
      const status = raw.status;
      let res = raw._data as ApiEnvelope<LoginData> | string | undefined;
      if (typeof res === "string") {
        try {
          res = JSON.parse(res) as ApiEnvelope<LoginData>;
        } catch {
          res = undefined;
        }
      }

      if (res && typeof res === "object" && res.error?.code === "timeout") {
        return {
          ok: false,
          message: userFacingApiError({ code: "timeout", message: res.error?.message, fallback: msgAuthTimeoutBe })
        };
      }
      if (status === 504) {
        return {
          ok: false,
          message:
            "Gateway timeout (504). Periksa API Gin, `WORKPULSE_AUTH_DB_QUERY_TIMEOUT` di BE, dan log server."
        };
      }

      if (!res || !res.ok || !res.data?.accessToken) {
        const code = res?.error?.code;
        const friendly = userFacingApiError({
          code,
          message: res?.error?.message,
          fallback: status === 401 ? "Email atau password salah." : "Login gagal. Coba lagi."
        });
        return { ok: false, message: friendly };
      }
      accessToken.value = res.data.accessToken;
      refreshToken.value = res.data.refreshToken ?? null;
      setAuth(true);
      persistWorkpulseSessionUser(res.data.user);
      persistWorkpulseSessionTokens(res.data.accessToken, res.data.refreshToken ?? null, { rememberMe });
      setRememberMePreference(rememberMe, email);
      const { applyFromLoginUser } = useWorkpulsePermissions();
      applyFromLoginUser(res.data.user);
      return { ok: true };
    } catch (e: unknown) {
      const err = e as { name?: string; message?: string };
      if (err?.name === "AbortError" || /aborted|timeout/i.test(String(err?.message || ""))) {
        return {
          ok: false,
          message: `${msgAuthTimeoutBe} (Tidak ada jawaban sebelum batas waktu di browser ~22 detik.)`
        };
      }
      const fe = e as {
        data?: ApiEnvelope<unknown>;
        statusCode?: number;
        status?: number;
        message?: string;
      };
      const code = fe.statusCode ?? fe.status;
      const msg = userFacingApiError({
        code: fe.data?.error?.code,
        message: fe.data?.error?.message,
        fallback:
          typeof fe.message === "string" && fe.message.trim() && !/fetch|network|failed/i.test(fe.message)
            ? fe.message.trim()
            : "Tidak bisa menghubungi server. Periksa koneksi Anda dan coba lagi."
      });
      return { ok: false, message: msg };
    } finally {
      clearTimeout(timer);
    }
  };

  /** `invalid` = refresh token tidak valid (harus login ulang); `failed` = jaringan/timeout (jangan paksa logout). */
  const refreshAccessTokenDetailed = async (): Promise<"ok" | "invalid" | "failed"> => {
    if (!import.meta.client) return "failed";
    const base = apiBase();
    if (!base) return "failed";
    const rt = refreshToken.value || readWorkpulseRefreshToken();

    try {
      const runRefresh = async (candidateRt: string | null) => {
        const raw = await $fetch.raw(`${base}/api/v1/auth/refresh`, {
          method: "POST",
          body: candidateRt ? { refreshToken: candidateRt } : {},
          credentials: "include",
          ignoreResponseError: true
        });

        let res = raw._data as ApiEnvelope<LoginData> | string | undefined;
        if (typeof res === "string") {
          try {
            res = JSON.parse(res) as ApiEnvelope<LoginData>;
          } catch {
            res = undefined;
          }
        }
        return { raw, res };
      };

      let activeRt: string | null = rt ?? null;
      let { raw, res } = await runRefresh(activeRt);
      let code = (res?.error?.code ?? "").toLowerCase();

      if (activeRt && (raw.status === 400 || raw.status === 401 || code === "invalid_refresh")) {
        ({ raw, res } = await runRefresh(null));
        code = (res?.error?.code ?? "").toLowerCase();
        activeRt = null;
      }

      if (raw.status === 401 || code === "invalid_refresh") return "invalid";
      if (raw.status !== 200 || !res?.ok || !res.data?.accessToken) return "failed";

      accessToken.value = res.data.accessToken;
      refreshToken.value = res.data.refreshToken ?? activeRt ?? null;
      setAuth(true);
      if (res.data.user) {
        persistWorkpulseSessionUser(res.data.user);
        const { applyFromLoginUser } = useWorkpulsePermissions();
        applyFromLoginUser(res.data.user);
      }
      persistWorkpulseSessionTokens(res.data.accessToken, res.data.refreshToken ?? activeRt ?? null, {
        rememberMe: readRememberMePreference()
      });
      return "ok";
    } catch {
      return "failed";
    }
  };

  /**
   * Perpanjang access token via refresh token (BE default access TTL ~15 menit).
   * Web Locks mencegah dua tab refresh bersamaan (BE memutar refresh token → tab kedua gagal).
   */
  const refreshAccessToken = async (): Promise<boolean> => {
    if (!import.meta.client) return false;

    const run = async (): Promise<"ok" | "invalid" | "failed"> => {
      const beforeRt = readWorkpulseRefreshToken();
      let result = await refreshAccessTokenDetailed();
      if (result === "ok") return "ok";
      if (result !== "invalid") return result;

      initTokensFromSession();
      const afterRt = readWorkpulseRefreshToken();
      if (afterRt && afterRt !== beforeRt) {
        result = await refreshAccessTokenDetailed();
        if (result === "ok") return "ok";
      }
      return result;
    };

    try {
      if (typeof navigator !== "undefined" && navigator.locks?.request) {
        return (await navigator.locks.request("workpulse-token-refresh", run)) === "ok";
      }
    } catch {
      /* Lock API tidak tersedia */
    }
    return (await run()) === "ok";
  };

  const logout = async () => {
    const base = apiBase();

    if (import.meta.client && base) {
      const ac = new AbortController();
      const timer = setTimeout(() => ac.abort(), 8000);
      try {
        const body: Record<string, unknown> = {};
        if (refreshToken.value) body.refreshToken = refreshToken.value;
        else {
          const rt = readWorkpulseRefreshToken();
          if (rt) body.refreshToken = rt;
        }

        await $fetch(`${base}/api/v1/auth/logout`, {
          method: "POST",
          body: Object.keys(body).length ? body : {},
          credentials: "include",
          signal: ac.signal,
          headers:
            accessToken.value || readWorkpulseAccessToken()
              ? { Authorization: `Bearer ${accessToken.value || readWorkpulseAccessToken()}` }
              : undefined
        });
      } catch {
        /* jaringan / cert / CORS */
      } finally {
        clearTimeout(timer);
      }
    }

    setAuth(false);
    setAccessToken(null);
    setRefreshToken(null);
    const { resetForLogout } = useWorkpulsePermissions();
    resetForLogout();
    clearWorkpulseSessionUser();
    await nextTick();
    await navigateTo("/login", { replace: true });
  };

  return {
    isAuthenticated,
    accessToken,
    refreshToken,
    initAuth,
    initTokensFromSession,
    signIn,
    signOut,
    setAccessToken,
    setRefreshToken,
    loginWithPassword,
    refreshAccessToken,
    refreshAccessTokenDetailed,
    logout
  };
};
