/*
 * auth.js
 * Copyright (C) 2025 veypi <i@veypi.com>
 *
 * Distributed under terms of the MIT license.
 */

class TokenService {
  #login_url = '/login'
  #refresh_url = '/api/token'
  constructor() {
    this.tokenKey = 'access';
    this.refreshTokenKey = 'refresh';
  }

  check(domain, id, level) {
    let body = this.body();
    if (!body || !body.access) {
      return false;
    }
    for (let a of body.access) {
      if (a.name === domain && (a.tid === '' || a.tid === id) && a.level >= level) {
        return true
      }
    }
    return false
  }

  setToken(token) {
    localStorage.setItem(this.tokenKey, token);
  }

  getToken() {
    return localStorage.getItem(this.tokenKey);
  }

  toJSON() {
    return this.getToken()
  }

  toString() {
    return this.getToken()
  }

  setRefreshToken(refreshToken) {
    localStorage.setItem(this.refreshTokenKey, refreshToken);
  }

  getRefreshToken() {
    return localStorage.getItem(this.refreshTokenKey);
  }

  clearToken() {
    localStorage.removeItem(this.tokenKey);
    localStorage.removeItem(this.refreshTokenKey);
  }

  hasToken() {
    return !!this.getToken();
  }

  parseToken(token) {
    try {
      if (!token || typeof token !== 'string') return null;
      const base64Url = token.split('.')[1];
      const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
      return JSON.parse(window.atob(base64));
    } catch (error) {
      console.info('Token解析失败:', error);
      return null;
    }
  }
  __cache = null
  body() {
    if (!this.__cache) {
      this.__cache = this.parseToken(this.getToken());
    }
    if (!this.__cache) {
      this.__cache = this.parseToken(this.getRefreshToken());
    }
    return this.__cache
  }
  logout(to, querys) {
    this.clearToken();
    let url = new URL(this.#login_url, window.location.origin)
    let redirect = to || window.location.pathname
    url.searchParams.set('redirect', redirect)
    for (let key in querys) {
      url.searchParams.set(key, querys[key]);
    }
    location.href = url.toString()
  }

  async refresh() {
    const refreshToken = this.getRefreshToken();
    if (!refreshToken) {
      this.logout()
      return;
    }
    try {
      let data = await fetch(this.#refresh_url, {
        method: 'post',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refresh: refreshToken })
      }).then(res => {
        if (res.status !== 200) {
          throw new Error(`Token刷新失败，状态码: ${res.status}`);
        }
        return res.text()
      })
      this.__cache = null; // 清除缓存
      this.setToken(data);
    } catch (e) {
      console.error('Token刷新失败:', e);
      this.clearToken()
      logout();
    }
  }
  isExpired() {
    const decoded = this.body();
    if (!decoded) return true;

    const currentTime = Date.now() / 1000;
    return decoded.exp < currentTime;
  }
  wrapAxios(instance) {

    // 定义一个标志，用于防止在刷新令牌时发送多个刷新请求
    let isRefreshing = false;
    // 定义一个队列，用于存储在刷新令牌期间失败的请求
    let failedQueue = [];

    /**
     * 处理等待队列中的请求。
     * 刷新令牌成功后，将队列中的请求重新发送；刷新失败则拒绝这些请求。
     * @param {Error|null} error - 如果刷新令牌失败，则为错误对象。
     * @param {string|null} token - 刷新成功后获取的新令牌。
     */
    const processQueue = (error, token = null) => {
      failedQueue.forEach(prom => {
        if (error) {
          // 刷新失败，拒绝队列中的所有请求
          prom.reject(error);
        } else {
          // 刷新成功，使用新令牌解决队列中的所有请求
          prom.resolve(token);
        }
      });
      // 清空队列
      failedQueue = [];
    };

    // 请求拦截器：在发送请求前添加认证令牌
    instance.interceptors.request.use(config => {
      const token = this.getToken();
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    }, error => {
      // 对请求错误做些什么
      return Promise.reject(error);
    });

    let that = this
    // 响应拦截器：处理响应数据，特别是针对 401 状态码进行令牌刷新和重试
    instance.interceptors.response.use((response) => {
      // 任何 2xx 范围内的状态码都会触发此函数
      // 这里可以添加其他全局的成功响应处理逻辑
      return response;
    }, async function(error) {
      // 任何超出 2xx 范围的状态码都会触发此函数
      const originalRequest = error.config;

      // 检查错误响应状态码是否为 401 (未授权)
      // 并且确保这不是一个已经重试过的请求 (通过 originalRequest._retry 标记)

      if (error.response && error.response.status === 401 && !originalRequest.noretry) {

        // 统计该请求的重试次数
        originalRequest.__retryCount = originalRequest.__retryCount || 0;
        originalRequest.__retryCount++;


        // 如果重试次数超过 3 次，则不再重试，直接跳转到登录页
        if (originalRequest.__retryCount >= 3) {
          // that.logout()
          // 拒绝原始请求的 Promise，停止后续处理
          return Promise.reject(error);
        }

        // 如果当前正在进行令牌刷新
        if (isRefreshing) {
          // 将当前失败的请求添加到队列中，等待新令牌
          return new Promise(resolve => {
            failedQueue.push({ resolve, reject: (err) => { throw err; } });
          }).then(token => {
            // 刷新成功后，使用新令牌更新请求头
            originalRequest.headers.Authorization = `Bearer ${token}`;
            // 重新发送原始请求
            return instance(originalRequest);
          }).catch(err => {
            // 如果队列中的请求被拒绝，则抛出错误
            return Promise.reject(err);
          });
        }

        // 如果没有正在进行令牌刷新，则设置标志为 true，开始刷新
        isRefreshing = true;

        try {
          // 发送请求来刷新令牌
          await that.refresh();
          const newToken = that.getToken();

          // 更新原始请求的 Authorization 头为新的令牌
          originalRequest.headers.Authorization = `Bearer ${newToken}`;
          // 处理等待队列中的所有请求，用新的令牌重新发送
          processQueue(null, newToken);
          // 重新发送最初导致 401 错误的请求
          return instance(originalRequest);
        } catch (refreshError) {
          // 如果刷新令牌本身也失败了 (例如，refresh token 已过期)
          // 清除本地令牌
          // 拒绝等待队列中的所有请求
          processQueue(refreshError);
          // that.clearToken();
          that.logout();
          // 拒绝原始请求的 Promise
          return Promise.reject(refreshError);
        } finally {
          // 无论成功或失败，最后都要将刷新标志重置为 false
          isRefreshing = false;
        }
      }
      // 对于其他类型的错误，或不是需要重试的 401 错误，直接拒绝 Promise
      return Promise.reject(error);
    });
  }
}

export default new TokenService();
