import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios'
import type { ApiResponse, TokenResponse } from '../types'

const REFRESH_BUFFER_MS = 60_000 // refresh 1 minute before access token expiry

const apiClient: AxiosInstance = axios.create({
  baseURL: '/api/v1',
  headers: {
    'Content-Type': 'application/json',
  },
  timeout: 30000,
})

function getStoredAccessToken(): string | null {
  return localStorage.getItem('vigilcare_token')
}

function getStoredRefreshToken(): string | null {
  return localStorage.getItem('vigilcare_refresh_token')
}

function setStoredTokens(accessToken: string, refreshToken: string): void {
  localStorage.setItem('vigilcare_token', accessToken)
  localStorage.setItem('vigilcare_refresh_token', refreshToken)
}

function clearStoredAuth(): void {
  localStorage.removeItem('vigilcare_token')
  localStorage.removeItem('vigilcare_refresh_token')
  localStorage.removeItem('vigilcare_user')
}

function decodeJwtExpiry(token: string): number | null {
  try {
    const payload = JSON.parse(atob(token.split('.')[1]!))
    return typeof payload.exp === 'number' ? payload.exp * 1000 : null
  } catch {
    return null
  }
}

let refreshPromise: Promise<string | null> | null = null

async function refreshAccessToken(): Promise<string | null> {
  const refreshToken = getStoredRefreshToken()
  if (!refreshToken) return null

  if (!refreshPromise) {
    refreshPromise = (async () => {
      try {
        const response = await axios.post<ApiResponse<TokenResponse>>(
          '/api/v1/auth/refresh',
          { refreshToken }
        )
        const data = response.data.data
        if (!response.data.success || !data) return null

        setStoredTokens(data.token, data.refreshToken)
        scheduleProactiveRefresh(data.token)
        return data.token
      } catch {
        return null
      } finally {
        refreshPromise = null
      }
    })()
  }

  return refreshPromise
}

let refreshTimer: ReturnType<typeof setTimeout> | null = null

function scheduleProactiveRefresh(accessToken: string): void {
  if (refreshTimer) clearTimeout(refreshTimer)

  const expiresAt = decodeJwtExpiry(accessToken)
  if (!expiresAt) return

  const delay = Math.max(expiresAt - Date.now() - REFRESH_BUFFER_MS, 0)
  refreshTimer = setTimeout(() => {
    void refreshAccessToken()
  }, delay)
}

export function initializeAuthRefresh(): void {
  const token = getStoredAccessToken()
  if (token) scheduleProactiveRefresh(token)
}

function redirectToLogin(): void {
  clearStoredAuth()
  window.location.href = '/login'
}

// Request interceptor: attach JWT from localStorage
apiClient.interceptors.request.use(
  async (config: InternalAxiosRequestConfig) => {
    const token = getStoredAccessToken()
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// Response interceptor: retry once on 401 after refresh
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }

    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true
      const newToken = await refreshAccessToken()
      if (newToken && originalRequest.headers) {
        originalRequest.headers.Authorization = `Bearer ${newToken}`
        return apiClient(originalRequest)
      }
      redirectToLogin()
    }

    return Promise.reject(error)
  }
)

// Typed API helpers
export async function get<T>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>> {
  const response = await apiClient.get<ApiResponse<T>>(url, { params })
  return response.data
}

export async function post<T>(
  url: string,
  data?: unknown,
  headers?: Record<string, string>
): Promise<ApiResponse<T>> {
  const response = await apiClient.post<ApiResponse<T>>(url, data, { headers })
  return response.data
}

export async function put<T>(url: string, data?: unknown): Promise<ApiResponse<T>> {
  const response = await apiClient.put<ApiResponse<T>>(url, data)
  return response.data
}

export async function patch<T>(url: string, data?: unknown): Promise<ApiResponse<T>> {
  const response = await apiClient.patch<ApiResponse<T>>(url, data)
  return response.data
}

export async function del<T>(url: string): Promise<ApiResponse<T>> {
  const response = await apiClient.delete<ApiResponse<T>>(url)
  return response.data
}

// Multipart upload helper — used by IntakeView for document upload
export async function uploadFile<T>(
  url: string,
  file: File,
  fields: Record<string, string>
): Promise<ApiResponse<T>> {
  const formData = new FormData()
  formData.append('file', file)
  Object.entries(fields).forEach(([key, value]) => {
    formData.append(key, value)
  })

  const response = await apiClient.post<ApiResponse<T>>(url, formData, {
    headers: { 'Content-Type': 'multipart/form-data' },
    timeout: 60000, // uploads may be larger — extend timeout
  })
  return response.data
}

/** POST JSON and receive a binary response (e.g. cover sheet PDF). */
export async function postBlob(url: string, data?: unknown): Promise<Blob> {
  const response = await apiClient.post(url, data, {
    responseType: 'blob',
    timeout: 60000,
  })
  return response.data as Blob
}

/** GET and receive a binary response (e.g. scanned document). */
export async function getBlob(url: string): Promise<Blob> {
  const response = await apiClient.get(url, { responseType: 'blob' })
  return response.data as Blob
}

export default apiClient