import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
import { environment } from '@environments/environment';
import { BehaviorSubject, Observable, throwError, tap, catchError } from 'rxjs';
import { firstValueFrom } from 'rxjs';

import { 
  User,
  UserType,
  UserRole,
} from '@core/models/dcb-bo-hub-user.model';

// === INTERFACES DTO AUTH ===
export interface LoginDto {
  username: string;
  password: string;
}

export interface RefreshTokenDto {
  refresh_token: string;
}

export interface LoginResponseDto {
  access_token: string;
  refresh_token: string;
  expires_in: number;
  token_type: string;
}

export interface LogoutResponseDto {
  message: string;
}

export interface AuthStatusResponseDto {
  authenticated: boolean;
  status: string;
}

export interface TokenValidationResponseDto {
  valid: boolean;
  user: {
    id: string;
    username: string;
    email: string;
    firstName: string;
    lastName: string;
    roles: string[];
  };
  expires_in: number;
}

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private readonly http = inject(HttpClient);
  
  private readonly tokenKey = 'access_token';
  private readonly refreshTokenKey = 'refresh_token';
  
  private authState$ = new BehaviorSubject<boolean>(this.isAuthenticated());
  private userProfile$ = new BehaviorSubject<User | null>(null);
  private initialized$ = new BehaviorSubject<boolean>(false);

  // === INITIALISATION DE L'APPLICATION ===

  /**
   * Initialise l'authentification au démarrage de l'application
   */
  async initialize(): Promise<boolean> {
    
    await new Promise(resolve => setTimeout(resolve, 0));
    
    try {
      const token = this.getAccessToken();
      
      if (!token) {
        setTimeout(() => {
          this.initialized$.next(true);
        });
        return false;
      }

      if (this.isTokenExpired(token)) {
        const refreshSuccess = await this.tryRefreshToken();
        setTimeout(() => {
          this.initialized$.next(true);
        });
        return refreshSuccess;
      }

      // Token valide : charger le profil utilisateur
      await firstValueFrom(this.loadUserProfile());
      
      setTimeout(() => {
        this.authState$.next(true);
        this.initialized$.next(true);
      });
      
      return true;

    } catch (error) {
      this.clearAuthData();
      setTimeout(() => {
        this.initialized$.next(true);
      });
      return false;
    }
  }

  /**
   * Tente de rafraîchir le token de manière synchrone
   */
  private async tryRefreshToken(): Promise<boolean> {
    const refreshToken = this.getRefreshToken();
    
    if (!refreshToken) {
      return false;
    }

    try {
      await firstValueFrom(this.refreshAccessToken());
      await firstValueFrom(this.loadUserProfile());
      this.authState$.next(true);
      return true;
    } catch (error) {
      this.clearAuthData();
      return false;
    }
  }

  /**
   * Observable pour suivre l'état d'initialisation
   */
  getInitializedState(): Observable<boolean> {
    return this.initialized$.asObservable();
  }

  // === MÉTHODES D'AUTHENTIFICATION ===

  /**
   * Connexion utilisateur
   */
  login(credentials: LoginDto): Observable<LoginResponseDto> {
    return this.http.post<LoginResponseDto>(
      `${environment.iamApiUrl}/auth/login`, 
      credentials
    ).pipe(
      tap(response => {
        this.handleLoginSuccess(response);
        this.loadUserProfile().subscribe();
      }),
      catchError(error => this.handleLoginError(error))
    );
  }

  /**
   * Rafraîchissement du token d'accès
   */
  refreshAccessToken(): Observable<LoginResponseDto> {
    const refreshToken = this.getRefreshToken();
    
    if (!refreshToken) {
      return throwError(() => new Error('No refresh token available'));
    }

    return this.http.post<LoginResponseDto>(
      `${environment.iamApiUrl}/auth/refresh`,
      { refresh_token: refreshToken }
    ).pipe(
      tap(response => this.handleLoginSuccess(response)),
      catchError(error => {
        this.clearAuthData();
        return throwError(() => error);
      })
    );
  }

  /**
   * Déconnexion utilisateur
   */
  logout(): Observable<LogoutResponseDto> {
    return this.http.post<LogoutResponseDto>(
      `${environment.iamApiUrl}/auth/logout`, 
      {}
    ).pipe(
      tap(() => this.clearAuthData()),
      catchError(error => {
        this.clearAuthData();
        return throwError(() => error);
      })
    );
  }

  /**
   * Chargement du profil utilisateur
   */
  loadUserProfile(): Observable<User> {
    return this.http.get<any>(
      `${environment.iamApiUrl}/auth/profile`
    ).pipe(
      tap(apiResponse => {
        // Déterminer le type d'utilisateur
        const userType = this.determineUserType(apiResponse);
        // Mapper vers le modèle User
        const userProfile = this.mapToUserModel(apiResponse, userType);
        
        this.userProfile$.next(userProfile);
      }),
      catchError(error => {
        console.error('❌ Erreur chargement profil:', error);
        return throwError(() => error);
      })
    );
  }

  /**
   * Détermine le type d'utilisateur basé sur la réponse API
   */
  private determineUserType(apiUser: any): UserType {

    const hubRoles = [UserRole.DCB_ADMIN || UserRole.DCB_SUPPORT];
    const merchantRoles = [UserRole.DCB_PARTNER || UserRole.DCB_PARTNER_ADMIN || UserRole.DCB_PARTNER_MANAGER || UserRole.DCB_PARTNER_SUPPORT];

    // Logique pour déterminer le type d'utilisateur
    if (apiUser.clientRoles?.[0].includes(merchantRoles)) {
      return UserType.MERCHANT_PARTNER;
    } else if (apiUser.clientRoles?.[0].includes(hubRoles)) {
      return UserType.HUB;
    } else {
      console.warn('Type d\'utilisateur non reconnu, rôle:', apiUser.clientRoles?.[0]);
      return UserType.HUB; // Fallback
    }
  }

  private mapToUserModel(apiUser: any, userType: UserType): User {
    const mappedUser: User = {
      id: apiUser.id || apiUser.userId || '',
      username: apiUser.username || apiUser.userName || '',
      email: apiUser.email || '',
      firstName: apiUser.firstName || apiUser.firstname || apiUser.given_name || '',
      lastName: apiUser.lastName || apiUser.lastname || apiUser.family_name || '',
      enabled: apiUser.enabled ?? apiUser.active ?? true,
      emailVerified: apiUser.emailVerified ?? apiUser.email_verified ?? false,
      userType: userType,
      merchantPartnerId: apiUser.merchantPartnerId || apiUser.partnerId || apiUser.merchantId || null,
      role: apiUser.clientRoles || apiUser.clientRoles?.[0] || '', // Gérer rôle unique ou tableau
      createdBy: apiUser.createdBy || apiUser.creatorId || null,
      createdByUsername: apiUser.createdByUsername || apiUser.creatorUsername || null,
      createdTimestamp: apiUser.createdTimestamp || apiUser.createdAt || apiUser.creationDate || Date.now(),
      lastLogin: apiUser.lastLogin || apiUser.lastLoginAt || apiUser.lastConnection || null
    };

    console.log('✅ Utilisateur mappé:', mappedUser);
    return mappedUser;
  }

  // === GESTION DE SESSION ===

  private handleLoginSuccess(response: LoginResponseDto): void {
    if (response.access_token) {
      localStorage.setItem(this.tokenKey, response.access_token);
      
      if (response.refresh_token) {
        localStorage.setItem(this.refreshTokenKey, response.refresh_token);
      }
      
      this.authState$.next(true);
    }
  }

  private clearAuthData(): void {
    localStorage.removeItem(this.tokenKey);
    localStorage.removeItem(this.refreshTokenKey);
    this.authState$.next(false);
    this.userProfile$.next(null);
  }

  // === VALIDATION DU TOKEN ===

  validateToken(): Observable<TokenValidationResponseDto> {
    return this.http.get<TokenValidationResponseDto>(
      `${environment.iamApiUrl}/auth/validate`
    );
  }

  // === OBSERVABLES POUR COMPOSANTS ===

  getAuthState(): Observable<boolean> {
    return this.authState$.asObservable();
  }

  getUserProfile(): Observable<User | null> {
    return this.userProfile$.asObservable();
  }

  // === GESTION DES RÔLES ET TYPES ===

  getCurrentUserRoles(): UserRole[] {
    const token = this.getAccessToken();
    if (!token) return [];

    try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      
      // Mapping des rôles Keycloak vers vos rôles DCB
      const roleMappings: { [key: string]: UserRole } = {
        // Rôles administrateur
        'admin': UserRole.DCB_ADMIN,
        'dcb-admin': UserRole.DCB_ADMIN,
        'administrator': UserRole.DCB_ADMIN,
        
        // Rôles support
        'support': UserRole.DCB_SUPPORT,
        'dcb-support': UserRole.DCB_SUPPORT,
        
        // Rôles partenaire
        'partner': UserRole.DCB_PARTNER,
        'dcb-partner': UserRole.DCB_PARTNER,
        
        // Rôles admin partenaire
        'partner-admin': UserRole.DCB_PARTNER_ADMIN,
        'dcb-partner-admin': UserRole.DCB_PARTNER_ADMIN,
        
        // Rôles manager partenaire
        'partner-manager': UserRole.DCB_PARTNER_MANAGER,
        'dcb-partner-manager': UserRole.DCB_PARTNER_MANAGER,
        
        // Rôles support partenaire
        'partner-support': UserRole.DCB_PARTNER_SUPPORT,
        'dcb-partner-support': UserRole.DCB_PARTNER_SUPPORT,
      };

      let allRoles: string[] = [];

      // Collecter tous les rôles du token
      if (payload.resource_access) {
        Object.values(payload.resource_access).forEach((client: any) => {
          if (client?.roles) {
            allRoles = allRoles.concat(client.roles);
          }
        });
      }

      if (payload.realm_access?.roles) {
        allRoles = allRoles.concat(payload.realm_access.roles);
      }

      const mappedRoles = allRoles
        .map(role => roleMappings[role.toLowerCase()])
        .filter(role => role !== undefined);

      return mappedRoles;

    } catch (error) {
      console.error('❌ Error:', error);
      return [];
    }
  }

  /**
   * Récupère le rôle principal de l'utilisateur courant
   */
  getCurrentUserRole(): UserRole | null {
    const roles = this.getCurrentUserRoles();
    return roles.length > 0 ? roles[0] : null;
  }

  /**
   * Récupère le type d'utilisateur courant
   */
  getCurrentUserType(): UserType | null {
    const role = this.getCurrentUserRole();
    if (!role) return null;

    // Déterminer le type d'utilisateur basé sur le rôle
    const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
    const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];

    if (hubRoles.includes(role)) {
      return UserType.HUB;
    } else if (merchantRoles.includes(role)) {
      return UserType.MERCHANT_PARTNER;
    }

    return null;
  }

  /**
   *  Récupère les clientRoles du profil utilisateur
   */
  getCurrentUserClientRoles(): UserRole | null {
    const profile = this.userProfile$.value;
    return profile?.role || null;
  }

  /**
   *  Récupère le rôle principal du profil utilisateur
   */
  getCurrentUserPrimaryRole(): UserRole | null {
    const clientRoles = this.getCurrentUserClientRoles();
    return clientRoles || null;
  }

  /**
   * Vérifie si l'utilisateur courant est un utilisateur Hub
   */
  isHubUser(): boolean {
    const profile = this.userProfile$.value;
    return profile?.userType === UserType.HUB;
  }

  /**
   * Vérifie si l'utilisateur courant est un utilisateur Marchand
   */
  isMerchantUser(): boolean {
    const profile = this.userProfile$.value;
    return profile?.userType === UserType.MERCHANT_PARTNER;
  }

  /**
   * Vérifie si l'utilisateur courant a un rôle spécifique
   */
  hasRole(role: UserRole): boolean {
    const clientRoles = this.getCurrentUserClientRoles();
    return clientRoles === role;
  }

  /**
   * Vérifie si l'utilisateur courant a au moins un des rôles spécifiés
   */
  hasAnyRole(role: UserRole): boolean {
    const userRoles = this.getCurrentUserClientRoles();
    return userRoles === role;
  }

  /**
   * Vérifie si l'utilisateur courant peut gérer les utilisateurs Hub
   */
  canManageHubUsers(): boolean {
    return this.hasAnyRole(UserRole.DCB_ADMIN) || this.hasAnyRole(UserRole.DCB_SUPPORT);
  }

  /**
   * Vérifie si l'utilisateur courant peut gérer les utilisateurs Marchands
   */
  canManageMerchantUsers(): boolean {
    return this.hasAnyRole(UserRole.DCB_ADMIN) || this.hasAnyRole(UserRole.DCB_PARTNER);
  }

  // === MÉTHODES UTILITAIRES ===

  onAuthState(): Observable<boolean> {
    return this.authState$.asObservable();
  }

  getProfile(): Observable<User | null> {
    return this.getUserProfile();
  }

  /**
   * Récupère l'ID de l'utilisateur courant
   */
  getCurrentUserId(): string | null {
    const profile = this.userProfile$.value;
    return profile?.id || null;
  }

  /**
   * Récupère le merchantPartnerId de l'utilisateur courant (si marchand)
   */
  getCurrentMerchantPartnerId(): string | null {
    const profile = this.userProfile$.value;
    return profile?.merchantPartnerId || null;
  }

  /**
   * Vérifie si le profil fourni est celui de l'utilisateur courant
   */
  isCurrentUserProfile(userId: string): boolean {
    const currentUserId = this.getCurrentUserId();
    return currentUserId === userId;
  }

  /**
   * Vérifie si l'utilisateur peut visualiser tous les marchands
   */
  canViewAllMerchants(): boolean {
    return this.hasAnyRole(UserRole.DCB_ADMIN) || this.hasAnyRole(UserRole.DCB_PARTNER);
  }

  // === TOKENS ===

  getAccessToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  getRefreshToken(): string | null {
    return localStorage.getItem(this.refreshTokenKey);
  }

  // === GESTION DES ERREURS ===

  private handleLoginError(error: HttpErrorResponse): Observable<never> {
    let errorMessage = 'Login failed';
    
    if (error.status === 401) {
      errorMessage = 'Invalid username or password';
    } else if (error.status === 403) {
      errorMessage = 'Account is disabled or not fully set up';
    } else if (error.error?.message) {
      errorMessage = error.error.message;
    }
    
    return throwError(() => new Error(errorMessage));
  }

  // === VERIFICATIONS ===

  isAuthenticated(): boolean {
    const token = this.getAccessToken();
    return !!token && !this.isTokenExpired(token);
  }

  private isTokenExpired(token: string): boolean {
    try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      const expiry = payload.exp;
      return (Math.floor((new Date).getTime() / 1000)) >= expiry;
    } catch {
      return true;
    }
  }
}