// http.js
import axios from "axios";
import { v4 as uuidv4 } from "uuid";

// ============ 配置 ============
const BASE_URL = import.meta.env.VITE_BASE_URL === "/"
  ? "/"
  : import.meta.env.VITE_BASE_URL.startsWith('localhost:')
    ? `http://${import.meta.env.VITE_BASE_URL}`
    : `https://${import.meta.env.VITE_BASE_URL}`;

const DB_CONFIG = {
  name: "TokenDB",
  version: 2,
  store: "tokens",
  key: "userToken",
} as const;

const STORAGE_KEY = "token";

// ============ IndexedDB 操作 ============
class TokenDB {
  private static async open(): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_CONFIG.name, DB_CONFIG.version);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        if (db.objectStoreNames.contains(DB_CONFIG.store)) {
          db.deleteObjectStore(DB_CONFIG.store);
        }
        db.createObjectStore(DB_CONFIG.store, { keyPath: "key" });
      };
    });
  }

  static async get(): Promise<string | null> {
    try {
      const db = await this.open();
      return new Promise((resolve) => {
        const tx = db.transaction(DB_CONFIG.store, "readonly");
        const request = tx.objectStore(DB_CONFIG.store).get(DB_CONFIG.key);
        request.onsuccess = () => resolve(request.result?.value || null);
        request.onerror = () => resolve(null);
        tx.oncomplete = () => db.close();
        tx.onabort = () => db.close();
      });
    } catch {
      return null;
    }
  }

  static async set(token: string): Promise<void> {
    try {
      const db = await this.open();
      return new Promise((resolve) => {
        const tx = db.transaction(DB_CONFIG.store, "readwrite");
        tx.objectStore(DB_CONFIG.store).put({ key: DB_CONFIG.key, value: token });
        tx.oncomplete = () => { db.close(); resolve(); };
        tx.onerror = () => { db.close(); resolve(); };
      });
    } catch {
      // 静默失败，有其他存储兜底
    }
  }
}

// ============ Token 管理器 ============
class TokenManager {
  private static cache: string | null = null;
  private static pending: Promise<string> | null = null;

  // UUID v4 格式校验，防止脏数据
  private static isValidToken(token: string | null): token is string {
    return !!token && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(token);
  }

  // 安全地操作 Storage
  private static safeGet(storage: Storage): string | null {
    try {
      return storage.getItem(STORAGE_KEY);
    } catch {
      return null;
    }
  }

  private static safeSet(storage: Storage, token: string): void {
    try {
      storage.setItem(STORAGE_KEY, token);
    } catch {
      // 静默失败
    }
  }

  // Cookie 操作（同步，iOS 上比 localStorage 更早可用）
  private static getFromCookie(): string | null {
    try {
      const match = document.cookie.match(new RegExp(`(?:^|; )${STORAGE_KEY}=([^;]*)`));
      return match ? decodeURIComponent(match[1]) : null;
    } catch {
      return null;
    }
  }

  private static saveToCookie(token: string): void {
    try {
      // 有效期 400 天（Safari 上限），SameSite=Lax 兼容 WebView
      document.cookie = `${STORAGE_KEY}=${encodeURIComponent(token)};path=/;max-age=34560000;SameSite=Lax`;
    } catch {
      // 静默失败
    }
  }

  // 同步到所有存储（后台执行，不阻塞）
  private static syncToAllStorages(token: string): void {
    this.safeSet(sessionStorage, token);
    this.safeSet(localStorage, token);
    this.saveToCookie(token);
    TokenDB.set(token).catch(() => { });
  }

  // 从同步存储快速获取（cookie 优先，iOS 上最可靠的同步读取）
  private static getFromSyncStorage(): string | null {
    const token = this.getFromCookie() || this.safeGet(sessionStorage) || this.safeGet(localStorage);
    return this.isValidToken(token) ? token : null;
  }

  // 延迟后重试读取同步存储（iOS 冷启动时存储可能未就绪）
  private static waitAndRetrySync(ms: number): Promise<string | null> {
    return new Promise(resolve => {
      setTimeout(() => resolve(this.getFromSyncStorage()), ms);
    });
  }

  // 主入口：获取或创建 Token
  static async getToken(): Promise<string> {
    // 1. 内存缓存（最快）
    if (this.cache) return this.cache;

    // 2. 等待进行中的创建（并发安全）
    if (this.pending) return this.pending;

    // 3. 同步存储快速路径
    const syncToken = this.getFromSyncStorage();
    if (syncToken) {
      this.cache = syncToken;
      this.syncToAllStorages(syncToken);
      return syncToken;
    }

    // 4. 异步获取或创建（带锁）
    this.pending = this.createToken();
    return this.pending;
  }

  private static async createToken(): Promise<string> {
    try {
      // 再次检查缓存
      if (this.cache) return this.cache;

      // 尝试从 IndexedDB 恢复
      const dbToken = await TokenDB.get();
      if (dbToken && this.isValidToken(dbToken)) {
        this.cache = dbToken;
        this.syncToAllStorages(dbToken);
        return dbToken;
      }

      // iOS 冷启动兜底：等待一小段时间后重试同步存储
      // （localStorage/cookie 数据可能存在，但初始化瞬间还未就绪）
      for (const delay of [50, 100, 150]) {
        const retryToken = await this.waitAndRetrySync(delay);
        if (retryToken) {
          this.cache = retryToken;
          this.syncToAllStorages(retryToken);
          return retryToken;
        }
      }

      // 所有恢复手段用尽，生成新 Token
      const newToken = uuidv4();
      this.cache = newToken;
      this.syncToAllStorages(newToken);
      return newToken;
    } finally {
      this.pending = null;
    }
  }
}

// ============ Axios 实例 ============
const http = axios.create({
  baseURL: BASE_URL,
  timeout: 15000,
});

// 请求拦截器
http.interceptors.request.use(
  async (config) => {
    const token = await TokenManager.getToken();
    config.headers["Token"] = token;
    config.headers["X-Token"] = token;
    config.params = { ...config.params, token };
    return config;
  },
  (error) => Promise.reject(error)
);

// 响应拦截器
http.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response) {
      console.error("Error:", error.response.status, error.response.data);
    } else {
      console.error("Error:", error.message);
    }
    return Promise.reject(error);
  }
);

export default http;
