/**
 * Live HTTP client for apps/backend envelope { code, message, data, error }.
 *
 * API base 規則（前後端要串得起來）：
 * 1. VITE_API_BASE 有設 → 用它（例如本機直連 http://127.0.0.1:8888）
 * 2. 未設 → 空字串 = same-origin，走 Vite proxy `/api` → gateway:8888
 *    （遠端開 threads-tool.30cm.net 時不能打瀏覽器本機 127.0.0.1）
 */

import { messageForApiCode } from "../../lib/apiErrors";

const SUCCESS = 102000;

export type Envelope<T = unknown> = {
  code: number;
  message: string;
  data: T;
  error: unknown;
};

export class ApiError extends Error {
  code: number;
  httpStatus: number;
  detail: unknown;
  /** 後端原始 message（除錯用；UI 請用 code → 語言包） */
  rawMessage: string;
  /**
   * 401 但 refresh 沒拿到結論（連不上／5xx）。代表登入狀態未知，不等於已登出，
   * 呼叫端不應該清 token 或把人踢回登入頁。
   */
  sessionUnknown: boolean;

  constructor(message: string, code: number, httpStatus: number, detail?: unknown, rawMessage?: string) {
    super(message);
    this.name = "ApiError";
    this.code = code;
    this.httpStatus = httpStatus;
    this.detail = detail;
    this.rawMessage = rawMessage ?? message;
    this.sessionUnknown = false;
  }
}

const TOKEN_KEY = "hb_live_tokens";

export type StoredTokens = {
  access_token: string;
  refresh_token: string;
  token_type?: string;
};

/**
 * Empty string → relative URLs (`/api/v1/...`) via Vite dev proxy.
 * Never default to 127.0.0.1 when the page is served from a remote host.
 */
export function getApiBase(): string {
  const env = (import.meta as unknown as { env?: Record<string, string> }).env;
  if (env && Object.prototype.hasOwnProperty.call(env, "VITE_API_BASE")) {
    const fromEnv = env.VITE_API_BASE ?? "";
    return String(fromEnv).replace(/\/$/, "");
  }
  // No env key: prefer same-origin proxy (works remote + local with vite proxy)
  return "";
}

export function loadTokens(): StoredTokens | null {
  try {
    const raw = localStorage.getItem(TOKEN_KEY);
    if (!raw) return null;
    return JSON.parse(raw) as StoredTokens;
  } catch {
    return null;
  }
}

export function saveTokens(t: StoredTokens | null): void {
  if (!t) {
    localStorage.removeItem(TOKEN_KEY);
    return;
  }
  localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
}

/**
 * refresh 的三種結果要分開，因為它們對呼叫端的意義完全不同：
 * - renewed：拿到新 token，重送原請求。
 * - rejected：後端明確說 refresh token 無效 → 真的登出，清 token。
 * - unreachable：連不上或後端掛了 → 登入狀態未知，token 要留著等下次重試。
 */
type RefreshResult =
  | { status: "renewed"; tokens: StoredTokens }
  | { status: "rejected" }
  | { status: "unreachable" };

let refreshPromise: Promise<RefreshResult> | null = null;

async function tryRefresh(): Promise<RefreshResult> {
  const cur = loadTokens();
  if (!cur?.refresh_token) return { status: "rejected" };
  if (!refreshPromise) {
    refreshPromise = (async (): Promise<RefreshResult> => {
      try {
        const res = await fetch(`${getApiBase()}/api/v1/auth/refresh`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ refresh_token: cur.refresh_token }),
        });
        // 5xx 是後端狀況，不是「這張 refresh token 壞了」。
        if (res.status >= 500) return { status: "unreachable" };
        const env = (await res.json()) as Envelope<{ tokens: StoredTokens }>;
        if (env.code !== SUCCESS || !env.data?.tokens) {
          saveTokens(null);
          return { status: "rejected" };
        }
        const next = env.data.tokens;
        saveTokens(next);
        return { status: "renewed", tokens: next };
      } catch {
        return { status: "unreachable" };
      } finally {
        refreshPromise = null;
      }
    })();
  }
  return refreshPromise;
}

export type RequestOpts = {
  method?: string;
  body?: unknown;
  auth?: boolean;
  /** skip auto-refresh retry */
  raw?: boolean;
  /** optional Accept-Language / custom headers */
  headers?: Record<string, string>;
};

function currentLocaleHeader(): string {
  try {
    const raw = localStorage.getItem("harbor.ui.prefs");
    if (!raw) return "zh-TW";
    const p = JSON.parse(raw) as { locale?: string };
    return p.locale === "en" ? "en" : "zh-TW";
  } catch {
    return "zh-TW";
  }
}

export async function apiRequest<T>(path: string, opts: RequestOpts = {}): Promise<T> {
  const method = opts.method ?? (opts.body !== undefined ? "POST" : "GET");
  const headers: Record<string, string> = {
    Accept: "application/json",
    "Accept-Language": currentLocaleHeader(),
    ...opts.headers,
  };
  if (opts.body !== undefined) {
    headers["Content-Type"] = "application/json";
  }
  const auth = opts.auth !== false;
  if (auth) {
    const tok = loadTokens();
    if (tok?.access_token) {
      headers.Authorization = `Bearer ${tok.access_token}`;
    }
  }

  const url = path.startsWith("http")
    ? path
    : `${getApiBase()}${path.startsWith("/") ? "" : "/"}${path}`;
  let res: Response;
  try {
    res = await fetch(url, {
      method,
      headers,
      body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
    });
  } catch (e) {
    throw new ApiError(messageForApiCode(0), 0, 0, e);
  }

  // retry once on 401 with refresh
  let sessionUnknown = false;
  if (res.status === 401 && auth && !opts.raw) {
    const refreshed = await tryRefresh();
    if (refreshed.status === "renewed") {
      headers.Authorization = `Bearer ${refreshed.tokens.access_token}`;
      try {
        res = await fetch(url, {
          method,
          headers,
          body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
        });
      } catch (e) {
        throw new ApiError(messageForApiCode(0), 0, 0, e);
      }
    } else {
      sessionUnknown = refreshed.status === "unreachable";
    }
  }

  const tag = (e: ApiError): ApiError => {
    e.sessionUnknown = sessionUnknown;
    return e;
  };

  let env: Envelope<T>;
  try {
    env = (await res.json()) as Envelope<T>;
  } catch {
    // go-zero 逾時常直接 503 非 envelope → 前端顯示「伺服器錯誤」；改成可理解的 AI 逾時
    if (res.status === 503 || res.status === 504 || res.status === 408) {
      throw tag(new ApiError(messageForApiCode(503001, "AI 回應逾時，請縮短內容後再試"), 503001, res.status));
    }
    throw tag(
      new ApiError(
        messageForApiCode(res.status >= 500 ? 500000 : 400001, `invalid response (${res.status})`),
        res.status,
        res.status,
      ),
    );
  }

  if (env.code !== SUCCESS) {
    // gateway 逾時有時仍帶 envelope + 5xx
    if (res.status === 503 || res.status === 504 || env.code === 503001) {
      throw tag(
        new ApiError(
          messageForApiCode(503001, env.message || "AI 回應逾時"),
          503001,
          res.status,
          env.error,
          env.message,
        ),
      );
    }
    // message 用目前語言包；code 保留給 UI 再對一次語系
    throw tag(
      new ApiError(messageForApiCode(env.code, env.message), env.code, res.status, env.error, env.message),
    );
  }
  return env.data;
}
