import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from "axios";
import { AuthLogger } from "@/lib/authLogger";
import { TokenManager } from "@/lib/auth/tokenManager";

const authLog = (event: string, detail: string) => {
  if (typeof window === "undefined") return;
  try {
    AuthLogger.log(event as any, detail);
  } catch {
    // 로거 실패해도 앱 동작에 영향 없음
  }
};

// API URL 동적 설정
// 우선순위: 1) 테넌트 서브도메인 → 직접 백엔드  2) 프로덕션 도메인  3) NEXT_PUBLIC_API_URL  4) default
//
// dev 가짜 서브도메인: <prefix>.<IPv4>(.nip.io|.sslip.io)? — Next rewrite 가 Host 변조하므로
// 같은 호스트 + backend 노출 포트(:8083) 로 직접 호출해 Host 보존.
const DEV_TENANT_HOST = /^[a-z0-9-]+\.\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\.(?:nip|sslip)\.io)?$/;

const getApiBaseUrl = (): string => {
  if (typeof window !== "undefined") {
    const currentHost = window.location.hostname;

    // 1. 테넌트 서브도메인 (*.invyone.com) 은 NEXT_PUBLIC_API_URL rewrite 우회 필수.
    //    rewrite 가 Host 헤더를 변조하면 SubdomainResolverFilter 파싱 실패.
    //    운영 Traefik 이 같은 호스트의 /api 를 backend 로 프록시 → 동일 호스트 상대 주소.
    if (currentHost.endsWith(".invyone.com")) {
      return `https://${currentHost}/api`;
    }

    // 1-b. dev 환경의 *.localhost 도 같은 이유로 직접 호출 (Host 헤더 보존).
    //      bare "localhost" 는 메타 컨텍스트(admin/cross-tenant)이므로 이 분기에서 제외.
    //      백엔드 dev 포트 8081 로 직결.
    if (currentHost.endsWith(".localhost") && currentHost !== "localhost") {
      return `http://${currentHost}:8081/api`;
    }

    // 1-c. dev 가짜 서브도메인 (<prefix>.<IPv4>(.nip.io|.sslip.io)?). 운영 Traefik 없으니 backend 노출 포트로 직접.
    if (DEV_TENANT_HOST.test(currentHost)) {
      return `http://${currentHost}:8083/api`;
    }

    // 2. 프로덕션 메인 도메인 fallback (1번에서 endsWith 로 이미 처리되므로 dead-code 가깝지만,
    //    invyone.com 루트 도메인 등 예외 케이스 보호용으로 유지)
    if (currentHost === "v1.invyone.com" || currentHost === "solution.invyone.com") {
      return "https://api.invyone.com/api";
    }
  }

  // 3. 환경변수 (docker-compose 에서 /api 로 주입되면 Next rewrite 사용)
  if (process.env.NEXT_PUBLIC_API_URL) {
    return process.env.NEXT_PUBLIC_API_URL;
  }

  // 4. 로컬 기본
  return "http://localhost:8081/api";
};

export const API_BASE_URL = getApiBaseUrl();

// 이미지 URL을 완전한 URL로 변환하는 함수
export const getFullImageUrl = (imagePath: string): string => {
  if (!imagePath) return "";

  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
    return imagePath;
  }

  if (imagePath.startsWith("/uploads")) {
    if (typeof window !== "undefined") {
      const currentHost = window.location.hostname;

      if (currentHost.endsWith(".invyone.com")) {
        return `https://api.invyone.com${imagePath}`;
      }

      if (currentHost === "localhost" || currentHost === "127.0.0.1") {
        return `http://localhost:8081${imagePath}`;
      }
    }

    const baseUrl = API_BASE_URL.replace(/\/api$/, "");
    if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) {
      return `${baseUrl}${imagePath}`;
    }

    return imagePath;
  }

  return imagePath;
};

// ============================================
// 토큰 갱신 로직 (중복 요청 방지)
// ============================================
let isRefreshing = false;
let refreshSubscribers: Array<(token: string) => void> = [];
let failedRefreshSubscribers: Array<(error: Error) => void> = [];

// 갱신 대기 중인 요청들에게 새 토큰 전달
const onTokenRefreshed = (newToken: string) => {
  refreshSubscribers.forEach((callback) => callback(newToken));
  refreshSubscribers = [];
  failedRefreshSubscribers = [];
};

// 갱신 실패 시 대기 중인 요청들에게 에러 전달
const onRefreshFailed = (error: Error) => {
  failedRefreshSubscribers.forEach((callback) => callback(error));
  refreshSubscribers = [];
  failedRefreshSubscribers = [];
};

// 갱신 완료 대기 Promise 등록
const waitForTokenRefresh = (): Promise<string> => {
  return new Promise((resolve, reject) => {
    refreshSubscribers.push(resolve);
    failedRefreshSubscribers.push(reject);
  });
};

const refreshToken = async (): Promise<string | null> => {
  try {
    const currentToken = TokenManager.getToken();
    if (!currentToken) {
      authLog("TOKEN_REFRESH_FAIL", "갱신 시도했으나 토큰 자체가 없음");
      return null;
    }

    authLog("TOKEN_REFRESH_START", `남은시간: ${Math.round(TokenManager.getTimeUntilExpiry(currentToken) / 60000)}분`);

    const response = await axios.post(
      `${API_BASE_URL}/auth/refresh`,
      {},
      {
        headers: {
          Authorization: `Bearer ${currentToken}`,
        },
      },
    );

    if (response.data?.success && response.data?.data?.token) {
      const newToken = response.data.data.token;
      TokenManager.setToken(newToken);
      authLog("TOKEN_REFRESH_SUCCESS", "토큰 갱신 완료");
      return newToken;
    }
    authLog("TOKEN_REFRESH_FAIL", `API 응답 실패: success=${response.data?.success}`);
    return null;
  } catch (err: any) {
    authLog("TOKEN_REFRESH_FAIL", `API 호출 에러: ${err?.response?.status || err?.message || "unknown"}`);
    return null;
  }
};

// ============================================
// 자동 토큰 갱신 (백그라운드)
// ============================================
let tokenRefreshTimer: ReturnType<typeof setInterval> | null = null;

const startAutoRefresh = (): void => {
  if (typeof window === "undefined") return;

  if (tokenRefreshTimer) {
    clearInterval(tokenRefreshTimer);
  }

  // 5분마다 토큰 상태 확인 (기존 10분 → 5분으로 단축)
  tokenRefreshTimer = setInterval(
    async () => {
      const token = TokenManager.getToken();
      if (!token) {
        // idle 페이지에서 토큰이 사라진 걸 interval 이 감지.
        // API 호출이 없으면 401 인터셉터가 발동하지 않으므로 여기서 직접 리다이렉트.
        // redirectToLogin 내부에서 /login 경로는 스킵.
        authLog("AUTO_REFRESH_CHECK", "interval 중 토큰 없음 감지 → 로그인 리다이렉트");
        redirectToLogin();
        return;
      }
      if (TokenManager.isTokenExpired(token)) {
        // 이미 만료 — 한 번 갱신 시도하고 실패하면 로그인으로
        const newToken = await refreshToken();
        if (!newToken) {
          authLog("REDIRECT_TO_LOGIN", "interval 중 만료 토큰 갱신 실패 → 로그인");
          redirectToLogin();
        }
        return;
      }
      if (TokenManager.isTokenExpiringSoon(token)) {
        await refreshToken();
      }
    },
    5 * 60 * 1000,
  );

  // 페이지 로드 시 즉시 확인
  const token = TokenManager.getToken();
  if (token && TokenManager.isTokenExpiringSoon(token)) {
    refreshToken();
  }
};

// 페이지 포커스 복귀 시 토큰 갱신 체크 (백그라운드 탭 throttle 대응)
const setupVisibilityRefresh = (): void => {
  if (typeof window === "undefined") return;

  document.addEventListener("visibilitychange", () => {
    if (!document.hidden) {
      const token = TokenManager.getToken();
      if (!token) {
        // 탭 복귀 시 토큰이 아예 없다 = 다른 탭 로그아웃 / 저장소 정리 / WebView 리셋 등.
        // API 호출이 일어나지 않는 idle 페이지라면 401 인터셉터가 안 발동하므로
        // 여기서 능동적으로 로그인 페이지로 보낸다. (redirectToLogin 내부에서
        // /login 경로면 no-op 이라 안전.)
        authLog("VISIBILITY_CHANGE", "탭 복귀 시 토큰 없음 → 로그인 리다이렉트");
        redirectToLogin();
        return;
      }

      if (TokenManager.isTokenExpired(token)) {
        authLog("VISIBILITY_CHANGE", "탭 복귀 시 토큰 만료 감지 → 갱신 시도");
        refreshToken().then((newToken) => {
          if (!newToken) {
            authLog("REDIRECT_TO_LOGIN", "탭 복귀 후 토큰 갱신 실패로 리다이렉트");
            redirectToLogin();
          }
        });
      } else if (TokenManager.isTokenExpiringSoon(token)) {
        authLog("VISIBILITY_CHANGE", "탭 복귀 시 토큰 만료 임박 → 갱신 시도");
        refreshToken();
      }
    }
  });
};

// 다른 탭에서 authToken 을 지우면 storage 이벤트가 발화한다 (같은 탭에서는 발화 X).
// 멀티탭 환경에서 한 탭의 로그아웃이 즉시 다른 탭으로 전파되도록 한다.
const setupStorageListener = (): void => {
  if (typeof window === "undefined") return;

  window.addEventListener("storage", (e) => {
    if (e.key !== "authToken") return;
    // newValue 가 null/빈 문자열이면 제거된 것. 새 값이 들어온 경우(다른 탭 로그인/갱신)는 그대로 따라감.
    if (!e.newValue) {
      authLog("STORAGE_SYNC", "다른 탭에서 authToken 제거 감지 → 로그인 리다이렉트");
      redirectToLogin();
    }
  });
};

// 사용자 활동 감지 기반 갱신
const setupActivityBasedRefresh = (): void => {
  if (typeof window === "undefined") return;

  let lastActivityCheck = Date.now();
  const activityThreshold = 5 * 60 * 1000; // 5분

  const handleActivity = (): void => {
    const now = Date.now();
    if (now - lastActivityCheck > activityThreshold) {
      const token = TokenManager.getToken();
      if (token && TokenManager.isTokenExpiringSoon(token)) {
        refreshToken();
      }
      lastActivityCheck = now;
    }
  };

  ["click", "keydown"].forEach((event) => {
    let throttleTimer: ReturnType<typeof setTimeout> | null = null;
    window.addEventListener(
      event,
      () => {
        if (!throttleTimer) {
          throttleTimer = setTimeout(() => {
            handleActivity();
            throttleTimer = null;
          }, 2000);
        }
      },
      { passive: true },
    );
  });
};

// 로그인 페이지 리다이렉트 (중복 방지)
let isRedirecting = false;
const redirectToLogin = (): void => {
  if (typeof window === "undefined") return;
  if (isRedirecting) return;
  if (window.location.pathname === "/login") return;

  authLog("REDIRECT_TO_LOGIN", `리다이렉트 실행 (from: ${window.location.pathname})`);
  isRedirecting = true;
  TokenManager.removeToken();
  window.location.href = "/login";
};

// 클라이언트 사이드에서 자동 갱신 시작
if (typeof window !== "undefined") {
  startAutoRefresh();
  setupVisibilityRefresh();
  setupActivityBasedRefresh();
  setupStorageListener();
}

// ============================================
// Axios 인스턴스 생성
// ============================================
export const apiClient = axios.create({
  baseURL: API_BASE_URL,
  timeout: 30000,
  headers: {
    "Content-Type": "application/json",
  },
  withCredentials: true,
});

// ============================================
// 요청 인터셉터
// ============================================
apiClient.interceptors.request.use(
  async (config: InternalAxiosRequestConfig) => {
    const token = TokenManager.getToken();

    if (token) {
      if (!TokenManager.isTokenExpired(token)) {
        config.headers.Authorization = `Bearer ${token}`;
      } else {
        authLog("TOKEN_EXPIRED_DETECTED", `요청 전 토큰 만료 감지 (${config.url}) → 갱신 시도`);
        const newToken = await refreshToken();
        if (newToken) {
          config.headers.Authorization = `Bearer ${newToken}`;
        } else {
          // 갱신 실패 = 세션 복구 불가. 로그인 페이지로 즉시 이동해야 호출부가
          // 무한 로딩에 빠지지 않음. redirectToLogin 은 isRedirecting 플래그로
          // 중복 호출을 막으므로 동시 요청이 여러 건이어도 안전함.
          authLog("TOKEN_REFRESH_FAIL", `요청 인터셉터에서 갱신 실패 → 로그인 리다이렉트 (${config.url})`);
          redirectToLogin();
          return Promise.reject(new Error("TOKEN_REFRESH_FAILED"));
        }
      }
    }

    // FormData 요청 시 Content-Type 자동 처리
    if (config.data instanceof FormData) {
      delete config.headers["Content-Type"];
    }

    // 언어 정보를 쿼리 파라미터에 추가 (GET 요청)
    if (config.method?.toUpperCase() === "GET") {
      let currentLang = "KR";

      if (typeof window !== "undefined") {
        if ((window as unknown as { __GLOBAL_USER_LANG?: string }).__GLOBAL_USER_LANG) {
          currentLang = (window as unknown as { __GLOBAL_USER_LANG: string }).__GLOBAL_USER_LANG;
        } else {
          const storedLocale = localStorage.getItem("userLocale");
          if (storedLocale) {
            currentLang = storedLocale;
          }
        }
      }

      if (config.params) {
        config.params.userLang = currentLang;
      } else {
        config.params = { userLang: currentLang };
      }
    }

    return config;
  },
  (error) => {
    return Promise.reject(error);
  },
);

// ============================================
// 응답 인터셉터
// ============================================
apiClient.interceptors.response.use(
  (response: AxiosResponse) => {
    // 백엔드에서 보내주는 새로운 토큰 처리
    const newToken = response.headers["x-new-token"];
    if (newToken) {
      TokenManager.setToken(newToken);
    }
    return response;
  },
  async (error: AxiosError) => {
    const status = error.response?.status;
    const url = error.config?.url;

    // 409 에러 (중복 데이터) - 조용하게 처리
    if (status === 409) {
      if (url?.includes("/check-duplicate") || url?.includes("/dataflow-diagrams")) {
        return Promise.reject(error);
      }
      return Promise.reject(error);
    }

    // 채번 규칙 미리보기 API 실패는 조용하게 처리
    if (url?.includes("/numbering-rules/") && url?.includes("/preview")) {
      return Promise.reject(error);
    }

    // 403 — AuthGuard 외 경로에서도 반드시 막히도록 전역 리다이렉트 (backend filter 와 쌍)
    if (status === 403 && typeof window !== "undefined") {
      const errorData = error.response?.data as { errorCode?: string };
      const ec = errorData?.errorCode;
      if (ec === "PASSWORD_CHANGE_REQUIRED" && window.location.pathname !== "/change-password") {
        authLog("REDIRECT_TO_CHANGE_PW", `403 PASSWORD_CHANGE_REQUIRED (${url})`);
        window.location.href = "/change-password";
        return Promise.reject(error);
      }
      if (ec === "CROSS_TENANT_REJECTED") {
        authLog("REDIRECT_TO_LOGIN", `403 CROSS_TENANT_REJECTED (${url})`);
        TokenManager.removeToken();
        if (window.location.pathname !== "/login") {
          window.location.href = "/login";
        }
        return Promise.reject(error);
      }
      if (ec === "TENANT_NOT_RESOLVED") {
        authLog("REDIRECT_TO_LOGIN", `403 TENANT_NOT_RESOLVED (${url})`);
        TokenManager.removeToken();
        if (window.location.pathname !== "/login") {
          window.location.href = "/login";
        }
        return Promise.reject(error);
      }
    }

    // 401 에러 처리 (핵심 개선)
    if (status === 401 && typeof window !== "undefined") {
      const errorData = error.response?.data as { error?: { code?: string; details?: string } };
      const errorCode = errorData?.error?.code;
      const errorDetails = errorData?.error?.details;
      const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };

      authLog("API_401_RECEIVED", `URL: ${url} | 코드: ${errorCode || "없음"} | 상세: ${errorDetails || "없음"}`);

      // 이미 재시도한 요청이면 로그인으로
      if (originalRequest?._retry) {
        authLog("REDIRECT_TO_LOGIN", `재시도 후에도 401 (${url}) → 로그인 리다이렉트`);
        redirectToLogin();
        return Promise.reject(error);
      }

      // 토큰 만료 에러 → 갱신 후 재시도
      if (errorCode === "TOKEN_EXPIRED" && originalRequest) {
        if (!isRefreshing) {
          isRefreshing = true;
          originalRequest._retry = true;

          try {
            authLog("API_401_RETRY", `토큰 만료로 갱신 후 재시도 (${url})`);
            const newToken = await refreshToken();
            if (newToken) {
              isRefreshing = false;
              onTokenRefreshed(newToken);
              originalRequest.headers.Authorization = `Bearer ${newToken}`;
              return apiClient.request(originalRequest);
            } else {
              isRefreshing = false;
              onRefreshFailed(new Error("토큰 갱신 실패"));
              authLog("REDIRECT_TO_LOGIN", `토큰 갱신 실패 (${url}) → 로그인 리다이렉트`);
              redirectToLogin();
              return Promise.reject(error);
            }
          } catch (refreshError) {
            isRefreshing = false;
            onRefreshFailed(refreshError as Error);
            authLog("REDIRECT_TO_LOGIN", `토큰 갱신 예외 (${url}) → 로그인 리다이렉트`);
            redirectToLogin();
            return Promise.reject(error);
          }
        } else {
          try {
            const newToken = await waitForTokenRefresh();
            originalRequest._retry = true;
            originalRequest.headers.Authorization = `Bearer ${newToken}`;
            return apiClient.request(originalRequest);
          } catch {
            return Promise.reject(error);
          }
        }
      }

      // TOKEN_MISSING, INVALID_TOKEN 등 → 로그인으로
      authLog("REDIRECT_TO_LOGIN", `복구 불가능한 인증 에러 (${errorCode || "UNKNOWN"}, ${url}) → 로그인 리다이렉트`);
      redirectToLogin();
    }

    return Promise.reject(error);
  },
);

// ============================================
// 공통 타입 및 헬퍼
// ============================================
export interface ApiResponse<T = unknown> {
  success: boolean;
  data?: T;
  message?: string;
  errorCode?: string;
}

export interface UserInfo {
  user_id: string;
  user_name: string;
  dept_name?: string;
  company_code?: string;
  user_type?: string;
  user_type_name?: string;
  email?: string;
  photo?: string;
  locale?: string;
  is_admin?: boolean;
}

export const getCurrentUser = async (): Promise<ApiResponse<UserInfo>> => {
  try {
    const response = await apiClient.get("/auth/me");
    return response.data;
  } catch (error: any) {
    return {
      success: false,
      message: error.response?.data?.message || error.message || "사용자 정보를 가져올 수 없습니다.",
      errorCode: error.response?.data?.errorCode,
    };
  }
};

export const apiCall = async <T>(
  method: "GET" | "POST" | "PUT" | "DELETE",
  url: string,
  data?: unknown,
): Promise<ApiResponse<T>> => {
  try {
    const response = await apiClient.request({
      method,
      url,
      data,
    });
    return response.data;
  } catch (error: unknown) {
    const axiosError = error as AxiosError;
    return {
      success: false,
      message:
        (axiosError.response?.data as { message?: string })?.message ||
        axiosError.message ||
        "알 수 없는 오류가 발생했습니다.",
      errorCode: (axiosError.response?.data as { errorCode?: string })?.errorCode,
    };
  }
};
