import axios, { type AxiosInstance } from 'axios';
import { z } from 'zod';
import type {
  ApiError,
  AuthTokens,
  LoginRequest,
  RegisterRequest,
  User,
  PaginatedResponse,
  Track,
  LibraryItem,
  Conversation,
} from '@/types';

// Configuration de base
const API_BASE_URL =
  import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api/v1';
const WS_BASE_URL = import.meta.env.VITE_WS_BASE_URL || 'ws://localhost:8081';

// Schémas de validation Zod
const UserSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string().email(),
  first_name: z.string().optional(),
  last_name: z.string().optional(),
  role: z.enum(['user', 'admin', 'super_admin']),
  is_active: z.boolean(),
  is_verified: z.boolean(),
  created_at: z.string(),
  last_login_at: z.string().optional(),
  avatar_url: z.string().optional(),
  bio: z.string().optional(),
});

const AuthTokensSchema = z.object({
  access_token: z.string(),
  refresh_token: z.string(),
  expires_in: z.number(),
});

const ApiErrorSchema = z.object({
  message: z.string(),
  code: z.string().optional(),
  details: z.record(z.string(), z.unknown()).optional(),
});

// Classe principale du service API
export class ApiService {
  private client: AxiosInstance;
  private refreshPromise: Promise<string> | null = null;

  constructor() {
    this.client = axios.create({
      baseURL: API_BASE_URL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    this.setupInterceptors();
  }

  private setupInterceptors() {
    // Intercepteur de requête pour ajouter le token
    this.client.interceptors.request.use(
      config => {
        const token = this.getAccessToken();
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      error => Promise.reject(error)
    );

    // Intercepteur de réponse pour gérer les erreurs 401
    this.client.interceptors.response.use(
      response => response,
      async error => {
        const originalRequest = error.config;

        if (error.response?.status === 401 && !originalRequest._retry) {
          originalRequest._retry = true;

          try {
            const newToken = await this.refreshAccessToken();
            originalRequest.headers.Authorization = `Bearer ${newToken}`;
            return this.client(originalRequest);
          } catch (refreshError) {
            this.clearTokens();
            window.location.href = '/login';
            return Promise.reject(refreshError);
          }
        }

        return Promise.reject(this.handleError(error));
      }
    );
  }

  private getAccessToken(): string | null {
    return localStorage.getItem('access_token');
  }

  private getRefreshToken(): string | null {
    return localStorage.getItem('refresh_token');
  }

  private setTokens(tokens: AuthTokens): void {
    localStorage.setItem('access_token', tokens.access_token);
    localStorage.setItem('refresh_token', tokens.refresh_token);
  }

  private clearTokens(): void {
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
  }

  private async refreshAccessToken(): Promise<string> {
    if (this.refreshPromise) {
      return this.refreshPromise;
    }

    this.refreshPromise = this.performRefresh();
    try {
      const token = await this.refreshPromise;
      return token;
    } finally {
      this.refreshPromise = null;
    }
  }

  private async performRefresh(): Promise<string> {
    const refreshToken = this.getRefreshToken();
    if (!refreshToken) {
      throw new Error('No refresh token available');
    }

    try {
      const response = await axios.post(`${API_BASE_URL}/auth/refresh`, {
        refresh_token: refreshToken,
      });

      const tokens = AuthTokensSchema.parse(response.data);
      this.setTokens(tokens);
      return tokens.access_token;
    } catch (error) {
      this.clearTokens();
      throw error;
    }
  }

  private handleError(error: any): ApiError {
    if (error.response?.data) {
      return ApiErrorSchema.parse(error.response.data);
    }
    return {
      message: error.message || 'An unexpected error occurred',
      code: 'UNKNOWN_ERROR',
    };
  }

  // Méthodes d'authentification
  async login(
    credentials: LoginRequest
  ): Promise<{ user: User; tokens: AuthTokens }> {
    const response = await this.client.post('/auth/login', credentials);
    // Backend retourne { user, token } (singulier)
    const { user, token } = response.data;

    const validatedUser = UserSchema.parse(user);
    const validatedTokens = AuthTokensSchema.parse(token);

    this.setTokens(validatedTokens);
    return { user: validatedUser, tokens: validatedTokens };
  }

  async register(
    userData: RegisterRequest
  ): Promise<{ user: User; tokens: AuthTokens }> {
    const response = await this.client.post('/auth/register', userData);
    // Backend retourne { user, token } (singulier)
    const { user, token } = response.data;

    const validatedUser = UserSchema.parse(user);
    const validatedTokens = AuthTokensSchema.parse(token);

    this.setTokens(validatedTokens);
    return { user: validatedUser, tokens: validatedTokens };
  }

  async logout(): Promise<void> {
    try {
      await this.client.post('/auth/logout');
    } finally {
      this.clearTokens();
    }
  }

  async getCurrentUser(): Promise<User> {
    const response = await this.client.get('/auth/me');
    return UserSchema.parse(response.data);
  }

  // Méthodes pour les utilisateurs
  async getUsers(params?: {
    page?: number;
    limit?: number;
    search?: string;
  }): Promise<PaginatedResponse<User>> {
    const response = await this.client.get('/users', { params });
    return response.data;
  }

  async getUser(id: number): Promise<User> {
    const response = await this.client.get(`/users/${id}`);
    return UserSchema.parse(response.data);
  }

  async updateUser(id: number, data: Partial<User>): Promise<User> {
    const response = await this.client.put(`/users/${id}`, data);
    return UserSchema.parse(response.data);
  }

  // Méthodes pour les tracks
  async getTracks(params?: {
    page?: number;
    limit?: number;
    search?: string;
  }): Promise<PaginatedResponse<Track>> {
    const response = await this.client.get('/tracks', { params });
    return response.data;
  }

  async getTrack(id: number): Promise<Track> {
    const response = await this.client.get(`/tracks/${id}`);
    return response.data;
  }

  async uploadTrack(
    file: File,
    metadata: { title: string; artist: string }
  ): Promise<Track> {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('title', metadata.title);
    formData.append('artist', metadata.artist);

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

  // Méthodes pour la bibliothèque
  async getLibraryItems(params?: {
    page?: number;
    limit?: number;
    type?: string;
  }): Promise<PaginatedResponse<LibraryItem>> {
    const response = await this.client.get('/library', { params });
    return response.data;
  }

  async uploadFile(
    file: File,
    metadata: { title: string; description?: string }
  ): Promise<LibraryItem> {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('title', metadata.title);
    if (metadata.description) {
      formData.append('description', metadata.description);
    }

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

  async toggleFavorite(itemId: string): Promise<LibraryItem> {
    const response = await this.client.post(`/library/${itemId}/favorite`);
    return response.data;
  }

  // Méthodes pour les messages
  async getMessages(
    conversationId: string,
    params?: { page?: number; limit?: number }
  ): Promise<PaginatedResponse<any>> {
    const response = await this.client.get(`/messages`, {
      params: { conversation_id: conversationId, ...params },
    });
    return response.data;
  }

  async sendMessage(
    conversationId: string,
    content: string,
    parentMessageId?: string
  ): Promise<any> {
    const response = await this.client.post('/messages', {
      conversation_id: conversationId,
      content,
      parent_message_id: parentMessageId,
    });
    return response.data;
  }

  // Méthodes pour les conversations
  async getConversations(): Promise<Conversation[]> {
    const response = await this.client.get('/conversations');
    // Backend retourne { conversations: [...], total: X }
    const conversations = response.data.conversations || [];
    
    // Convertir les IDs de int64 à string pour le frontend
    return conversations.map((conv: any) => ({
      ...conv,
      id: String(conv.id), // Backend retourne int64, frontend attend string
      name: conv.name || `Conversation ${conv.id}`,
      participants: conv.participants || [],
    }));
  }

  async createConversation(params: {
    name: string;
    description?: string;
    type?: 'public' | 'private' | 'direct';
    is_private?: boolean;
  }): Promise<Conversation> {
    const response = await this.client.post('/conversations', {
      name: params.name,
      description: params.description || '',
      type: params.type || 'public',
      is_private: params.is_private || false,
    });
    
    // Convertir la réponse backend en format frontend
    const data = response.data;
    return {
      id: String(data.id), // Backend retourne int64, frontend attend string
      name: data.name,
      description: data.description,
      type: data.type === 'public' || data.type === 'private' || data.type === 'direct' 
        ? data.type 
        : 'public',
      is_private: data.is_private || false,
      created_by: data.created_by,
      participants: data.participants || [],
      created_at: data.created_at,
      updated_at: data.updated_at,
    };
  }

  // Méthodes utilitaires
  getWebSocketUrl(): string {
    const token = this.getAccessToken();
    return `${WS_BASE_URL}?token=${token}`;
  }

  isAuthenticated(): boolean {
    return !!this.getAccessToken();
  }
}

// Instance singleton
export const apiService = new ApiService();
