/**
 * API V2 Client - External Authentication & Task Management
 *
 * Features:
 * - External Azure AD authentication
 * - Task history and management
 * - User task isolation
 * - Session management
 */

import axios, { AxiosError } from 'axios'
import type { AxiosInstance } from 'axios'
import type {
  LoginRequest,
  ApiError,
} from '@/types/api'
import type {
  LoginResponseV2,
  UserInfo,
  TaskCreate,
  TaskUpdate,
  Task,
  TaskDetail,
  TaskListResponse,
  TaskStats,
  SessionInfo,
  SystemStats,
  UserWithStats,
  TopUser,
  AuditLog,
  AuditLogListResponse,
  UserActivitySummary,
} from '@/types/apiV2'

/**
 * API Client Configuration
 * - In Docker: VITE_API_BASE_URL is empty string, use relative path
 * - In development: Use VITE_API_BASE_URL from .env or default to localhost:8000
 */
const envApiBaseUrl = import.meta.env.VITE_API_BASE_URL
const API_BASE_URL = envApiBaseUrl !== undefined ? envApiBaseUrl : 'http://localhost:8000'
const API_VERSION = 'v2'

class ApiClientV2 {
  private client: AxiosInstance
  private token: string | null = null
  private userInfo: UserInfo | null = null
  private tokenExpiresAt: number | null = null
  private refreshTimer: NodeJS.Timeout | null = null
  private isRefreshing: boolean = false
  private refreshFailed: boolean = false

  constructor() {
    this.client = axios.create({
      baseURL: `${API_BASE_URL}/api/${API_VERSION}`,
      timeout: 30000,
      headers: {
        'Content-Type': 'application/json',
      },
    })

    // Request interceptor to add auth token
    this.client.interceptors.request.use(
      (config) => {
        if (this.token) {
          config.headers.Authorization = `Bearer ${this.token}`
        }
        return config
      },
      (error) => Promise.reject(error)
    )

    // Response interceptor for error handling
    this.client.interceptors.response.use(
      (response) => response,
      async (error: AxiosError<ApiError>) => {
        if (error.response?.status === 401) {
          // If refresh has already failed, don't try again - just redirect
          if (this.refreshFailed) {
            console.warn('Refresh already failed, redirecting to login')
            this.clearAuth()
            window.location.href = '/login'
            return Promise.reject(error)
          }

          // If already refreshing, reject immediately to prevent retry storm
          if (this.isRefreshing) {
            console.warn('Token refresh already in progress, rejecting request')
            return Promise.reject(error)
          }

          // Token expired or invalid
          const detail = error.response?.data?.detail
          if (detail?.includes('Session expired') || detail?.includes('Invalid session')) {
            console.warn('Session expired, attempting refresh')
            this.isRefreshing = true

            try {
              await this.refreshToken()
              this.isRefreshing = false

              // Retry the original request only if refresh succeeded
              if (error.config) {
                return this.client.request(error.config)
              }
            } catch (refreshError) {
              console.error('Token refresh failed, redirecting to login')
              this.isRefreshing = false
              this.refreshFailed = true
              this.clearAuth()
              window.location.href = '/login'
              return Promise.reject(error)
            }
          } else {
            this.clearAuth()
            window.location.href = '/login'
          }
        }
        return Promise.reject(error)
      }
    )

    // Load auth data from localStorage
    this.loadAuth()
  }

  /**
   * Set authentication data
   */
  setAuth(token: string, user: UserInfo, expiresIn?: number) {
    this.token = token
    this.userInfo = user
    localStorage.setItem('auth_token_v2', token)
    localStorage.setItem('user_info_v2', JSON.stringify(user))

    // Schedule token refresh if expiresIn is provided
    if (expiresIn) {
      this.tokenExpiresAt = Date.now() + expiresIn * 1000
      localStorage.setItem('token_expires_at', this.tokenExpiresAt.toString())
      this.scheduleTokenRefresh(expiresIn)
    }
  }

  /**
   * Clear authentication data
   */
  clearAuth() {
    this.token = null
    this.userInfo = null
    this.tokenExpiresAt = null
    this.isRefreshing = false
    this.refreshFailed = false

    // Clear refresh timer
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer)
      this.refreshTimer = null
    }

    localStorage.removeItem('auth_token_v2')
    localStorage.removeItem('user_info_v2')
    localStorage.removeItem('token_expires_at')
  }

  /**
   * Load auth data from localStorage
   */
  private loadAuth() {
    const token = localStorage.getItem('auth_token_v2')
    const userInfoStr = localStorage.getItem('user_info_v2')
    const expiresAtStr = localStorage.getItem('token_expires_at')

    if (token && userInfoStr) {
      try {
        this.token = token
        this.userInfo = JSON.parse(userInfoStr)

        // Load and check token expiry
        if (expiresAtStr) {
          this.tokenExpiresAt = parseInt(expiresAtStr, 10)
          const timeUntilExpiry = this.tokenExpiresAt - Date.now()

          // If token is expired, clear auth
          if (timeUntilExpiry <= 0) {
            console.warn('Token expired, clearing auth')
            this.clearAuth()
            return
          }

          // Schedule refresh if token is expiring soon
          const refreshBuffer = 5 * 60 * 1000 // 5 minutes
          if (timeUntilExpiry < refreshBuffer) {
            console.log('Token expiring soon, refreshing immediately')
            this.refreshToken().catch(() => this.clearAuth())
          } else {
            // Schedule refresh for later
            this.scheduleTokenRefresh(Math.floor(timeUntilExpiry / 1000))
          }
        }
      } catch (error) {
        console.error('Failed to parse user info from localStorage:', error)
        this.clearAuth()
      }
    }
  }

  /**
   * Check if user is authenticated
   */
  isAuthenticated(): boolean {
    return this.token !== null && this.userInfo !== null
  }

  /**
   * Get current user info
   */
  getCurrentUser(): UserInfo | null {
    return this.userInfo
  }

  /**
   * Schedule token refresh before expiration
   * @param expiresIn - Token expiry time in seconds
   */
  private scheduleTokenRefresh(expiresIn: number): void {
    // Clear existing timer
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer)
    }

    // Schedule refresh 5 minutes before expiry
    const refreshBuffer = 5 * 60 // 5 minutes in seconds
    const refreshTime = Math.max(0, expiresIn - refreshBuffer) * 1000 // Convert to milliseconds

    console.log(`Scheduling token refresh in ${refreshTime / 1000} seconds`)

    this.refreshTimer = setTimeout(() => {
      console.log('Auto-refreshing token')
      this.refreshToken().catch((error) => {
        console.error('Auto token refresh failed:', error)
        // Don't redirect on auto-refresh failure, let user continue
        // Redirect will happen on next API call with 401
      })
    }, refreshTime)
  }

  /**
   * Refresh access token
   */
  private async refreshToken(): Promise<void> {
    try {
      const response = await this.client.post<LoginResponseV2>('/auth/refresh')

      // Update token and schedule next refresh
      this.setAuth(response.data.access_token, response.data.user, response.data.expires_in)

      console.log('Token refreshed successfully')
    } catch (error) {
      console.error('Token refresh failed:', error)
      throw error
    }
  }

  // ==================== Authentication ====================

  /**
   * Login via external Azure AD API
   */
  async login(data: LoginRequest): Promise<LoginResponseV2> {
    const response = await this.client.post<LoginResponseV2>('/auth/login', {
      username: data.username,
      password: data.password,
    })

    // Store token and user info with auto-refresh
    this.setAuth(response.data.access_token, response.data.user, response.data.expires_in)

    return response.data
  }

  /**
   * Logout (invalidate session)
   */
  async logout(sessionId?: number): Promise<void> {
    try {
      await this.client.post('/auth/logout', { session_id: sessionId })
    } finally {
      // Always clear local auth data
      this.clearAuth()
    }
  }

  /**
   * Get current user info from server
   */
  async getMe(): Promise<UserInfo> {
    const response = await this.client.get<UserInfo>('/auth/me')
    this.userInfo = response.data
    localStorage.setItem('user_info_v2', JSON.stringify(response.data))
    return response.data
  }

  /**
   * List user sessions
   */
  async listSessions(): Promise<SessionInfo[]> {
    const response = await this.client.get<{ sessions: SessionInfo[] }>('/auth/sessions')
    return response.data.sessions
  }

  // ==================== File Upload ====================

  /**
   * Upload a file
   */
  async uploadFile(file: File): Promise<{ task_id: string; filename: string; file_size: number; file_type: string; status: string }> {
    const formData = new FormData()
    formData.append('file', file)

    const response = await this.client.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
    })

    return response.data
  }

  // ==================== Task Management ====================

  /**
   * Create a new task
   */
  async createTask(data: TaskCreate): Promise<Task> {
    const response = await this.client.post<Task>('/tasks/', data)
    return response.data
  }

  /**
   * List tasks with pagination and filtering
   */
  async listTasks(params: {
    status?: 'pending' | 'processing' | 'completed' | 'failed'
    filename?: string
    date_from?: string
    date_to?: string
    page?: number
    page_size?: number
    order_by?: string
    order_desc?: boolean
  } = {}): Promise<TaskListResponse> {
    const response = await this.client.get<TaskListResponse>('/tasks/', { params })
    return response.data
  }

  /**
   * Get task statistics
   */
  async getTaskStats(): Promise<TaskStats> {
    const response = await this.client.get<TaskStats>('/tasks/stats')
    return response.data
  }

  /**
   * Get task details by ID
   */
  async getTask(taskId: string): Promise<TaskDetail> {
    const response = await this.client.get<TaskDetail>(`/tasks/${taskId}`)
    return response.data
  }

  /**
   * Update task
   */
  async updateTask(taskId: string, data: TaskUpdate): Promise<Task> {
    const response = await this.client.patch<Task>(`/tasks/${taskId}`, data)
    return response.data
  }

  /**
   * Delete task
   */
  async deleteTask(taskId: string): Promise<void> {
    await this.client.delete(`/tasks/${taskId}`)
  }

  /**
   * Start task processing
   */
  async startTask(taskId: string): Promise<Task> {
    const response = await this.client.post<Task>(`/tasks/${taskId}/start`)
    return response.data
  }

  /**
   * Cancel task
   */
  async cancelTask(taskId: string): Promise<Task> {
    const response = await this.client.post<Task>(`/tasks/${taskId}/cancel`)
    return response.data
  }

  /**
   * Retry failed task
   */
  async retryTask(taskId: string): Promise<Task> {
    const response = await this.client.post<Task>(`/tasks/${taskId}/retry`)
    return response.data
  }

  // ==================== Helper Methods ====================

  /**
   * Download file from task result
   */
  async downloadTaskFile(url: string, filename: string): Promise<void> {
    const response = await this.client.get(url, {
      responseType: 'blob',
    })

    // Create download link
    const blob = new Blob([response.data])
    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = filename
    link.click()
    window.URL.revokeObjectURL(link.href)
  }

  /**
   * Download task result as JSON
   */
  async downloadJSON(taskId: string): Promise<void> {
    const response = await this.client.get(`/tasks/${taskId}/download/json`, {
      responseType: 'blob',
    })

    const blob = new Blob([response.data], { type: 'application/json' })
    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = `${taskId}_result.json`
    link.click()
    window.URL.revokeObjectURL(link.href)
  }

  /**
   * Download task result as Markdown
   */
  async downloadMarkdown(taskId: string): Promise<void> {
    const response = await this.client.get(`/tasks/${taskId}/download/markdown`, {
      responseType: 'blob',
    })

    const blob = new Blob([response.data], { type: 'text/markdown' })
    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = `${taskId}_result.md`
    link.click()
    window.URL.revokeObjectURL(link.href)
  }

  /**
   * Download task result as PDF
   */
  async downloadPDF(taskId: string): Promise<void> {
    const response = await this.client.get(`/tasks/${taskId}/download/pdf`, {
      responseType: 'blob',
    })

    const blob = new Blob([response.data], { type: 'application/pdf' })
    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = `${taskId}_result.pdf`
    link.click()
    window.URL.revokeObjectURL(link.href)
  }

  // ==================== Admin APIs ====================

  /**
   * Get system statistics (admin only)
   */
  async getSystemStats(): Promise<SystemStats> {
    const response = await this.client.get<SystemStats>('/admin/stats')
    return response.data
  }

  /**
   * Get user list with statistics (admin only)
   */
  async listUsers(params: {
    page?: number
    page_size?: number
  } = {}): Promise<{ users: UserWithStats[]; total: number; page: number; page_size: number }> {
    const response = await this.client.get('/admin/users', { params })
    return response.data
  }

  /**
   * Get top users by metric (admin only)
   */
  async getTopUsers(params: {
    metric?: 'tasks' | 'completed_tasks'
    limit?: number
  } = {}): Promise<TopUser[]> {
    const response = await this.client.get<TopUser[]>('/admin/users/top', { params })
    return response.data
  }

  /**
   * Get audit logs (admin only)
   */
  async getAuditLogs(params: {
    user_id?: number
    category?: string
    action?: string
    success?: boolean
    date_from?: string
    date_to?: string
    page?: number
    page_size?: number
  } = {}): Promise<AuditLogListResponse> {
    const response = await this.client.get<AuditLogListResponse>('/admin/audit-logs', { params })
    return response.data
  }

  /**
   * Get user activity summary (admin only)
   */
  async getUserActivitySummary(userId: number, days: number = 30): Promise<UserActivitySummary> {
    const response = await this.client.get<UserActivitySummary>(
      `/admin/audit-logs/user/${userId}/summary`,
      { params: { days } }
    )
    return response.data
  }
}

// Export singleton instance
export const apiClientV2 = new ApiClientV2()
