import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, of, forkJoin } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { Agent, AgentStatus } from '../interfaces/agent';
import { TpeDevice, TpeStatus, TpeType } from '../interfaces/tpe';
import { environment } from 'src/environments/environment.development';
import { normalizePage } from '@shared/paging/normalize-page';
import { ListParams, PagedResult } from '@shared/paging/paging';

const USE_SERVER = true;
const API_BASE = '/api/agents';

// Interface to match the API response structure for TPE (nested in Agent)
// Note: When TPE is nested in Agent's tpes array, the agent field might be omitted or be a reference
interface TpeApiResponse {
  id: number;
  imei: string;
  serial: string;
  type: string;
  marque: string;
  modele: string;
  statut: string;
  agent?: any; // Can be Agent object or string reference, we'll handle it in transformTpe
  assigne: boolean;
  createdAt?: string;
  updatedAt?: string;
}

// Interface to match the API response structure
interface AgentApiResponse {
  id: number;
  code: string;
  profile: string;
  principalCode?: string;
  caisseProfile?: string;
  statut: string;
  zone?: string;
  kiosk?: string;
  fonction?: string;
  dateEmbauche?: string;
  nom: string;
  prenom: string;
  autresNoms?: string;
  dateNaissance?: string;
  lieuNaissance?: string;
  ville?: string;
  adresse?: string;
  autoriserAides?: boolean;
  phone: string;
  pin?: string;
  limiteInferieure?: number;
  limiteSuperieure?: number;
  limiteParTransaction?: number;
  limiteMinAirtime?: number;
  limiteMaxAirtime?: number;
  maxPeripheriques?: number;
  limitId?: number;
  nationalite?: string;
  cni?: string;
  cniDelivreeLe?: string;
  cniDelivreeA?: string;
  residence?: string;
  autreAdresse1?: string;
  statutMarital?: string;
  epoux?: string;
  autreTelephone?: string;
  tpes?: TpeApiResponse[];
  createdAt?: string;
  updatedAt?: string;
  createdBy?: string;
}

@Injectable({ providedIn: 'root' })
export class AgentService {
  private apiUrl = environment.apiBaseUrl + API_BASE;

  constructor(private http: HttpClient) {}

  // Helper method to get ngrok bypass headers
  private getNgrokHeaders(): Record<string, string> {
    const isNgrok =
      environment.apiBaseUrl.includes('ngrok-free.app') ||
      environment.apiBaseUrl.includes('ngrok.io') ||
      environment.apiBaseUrl.includes('ngrok');
    return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {};
  }

  // Transform API TPE response to TpeDevice
  private transformTpe(apiTpe: TpeApiResponse): TpeDevice {
    const transformStatut = (apiStatut: string): TpeStatus => {
      const upperStatut = apiStatut.toUpperCase() as TpeStatus;
      const validStatuses: TpeStatus[] = [
        'VALIDE',
        'INVALIDE',
        'EN_PANNE',
        'BLOQUE',
        'DISPONIBLE',
        'AFFECTE',
        'EN_MAINTENANCE',
        'HORS_SERVICE',
        'VOLE',
      ];
      return validStatuses.includes(upperStatut) ? upperStatut : 'INVALIDE';
    };

    // Transform agent if it's an object (not just a string reference)
    let transformedAgent: Agent | undefined = undefined;
    if (apiTpe.agent && typeof apiTpe.agent === 'object' && apiTpe.agent.id) {
      // If agent is a full object, transform it
      transformedAgent = this.transformAgent(apiTpe.agent as any);
    }

    return {
      id: String(apiTpe.id),
      imei: apiTpe.imei,
      serial: apiTpe.serial,
      type: apiTpe.type as TpeType,
      marque: apiTpe.marque,
      modele: apiTpe.modele,
      statut: transformStatut(apiTpe.statut),
      agent: transformedAgent,
      assigne: apiTpe.assigne,
      createdAt: apiTpe.createdAt,
      updatedAt: apiTpe.updatedAt,
    };
  }

  // Transform API response to Agent
  private transformAgent(apiAgent: AgentApiResponse): Agent {
    return {
      id: String(apiAgent.id),
      code: apiAgent.code,
      profile: apiAgent.profile,
      principalCode: apiAgent.principalCode,
      caisseProfile: apiAgent.caisseProfile,
      statut: apiAgent.statut as AgentStatus,
      zone: apiAgent.zone,
      kiosk: apiAgent.kiosk,
      fonction: apiAgent.fonction,
      dateEmbauche: apiAgent.dateEmbauche,
      nom: apiAgent.nom,
      prenom: apiAgent.prenom,
      autresNoms: apiAgent.autresNoms,
      dateNaissance: apiAgent.dateNaissance,
      lieuNaissance: apiAgent.lieuNaissance,
      ville: apiAgent.ville,
      adresse: apiAgent.adresse,
      autoriserAides: apiAgent.autoriserAides,
      phone: apiAgent.phone,
      pin: apiAgent.pin,
      limiteInferieure: apiAgent.limiteInferieure,
      limiteSuperieure: apiAgent.limiteSuperieure,
      limiteParTransaction: apiAgent.limiteParTransaction,
      limiteMinAirtime: apiAgent.limiteMinAirtime,
      limiteMaxAirtime: apiAgent.limiteMaxAirtime,
      maxPeripheriques: apiAgent.maxPeripheriques,
      limitId: apiAgent.limitId ? String(apiAgent.limitId) : undefined,
      nationalite: apiAgent.nationalite,
      cni: apiAgent.cni,
      cniDelivreeLe: apiAgent.cniDelivreeLe,
      cniDelivreeA: apiAgent.cniDelivreeA,
      residence: apiAgent.residence,
      autreAdresse1: apiAgent.autreAdresse1,
      statutMarital: apiAgent.statutMarital,
      epoux: apiAgent.epoux,
      autreTelephone: apiAgent.autreTelephone,
      tpes: apiAgent.tpes?.map((tpe) => {
        const transformed = this.transformTpe(tpe);
        return transformed;
      }),
      createdAt: apiAgent.createdAt,
      updatedAt: apiAgent.updatedAt,
      createdBy: apiAgent.createdBy,
    };
  }

  // Helper method to convert date string to LocalDateTime format (YYYY-MM-DDTHH:mm:ss)
  private formatDateForApi(dateStr: string | undefined): string | undefined {
    if (!dateStr) return undefined;
    // If already in ISO format with time, return as is
    if (dateStr.includes('T') || dateStr.includes(' ')) {
      return dateStr;
    }
    // If only date (YYYY-MM-DD), add time component
    if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) {
      return `${dateStr}T00:00:00`;
    }
    return dateStr;
  }

  // Transform Agent to API payload
  private transformToApiPayload(agent: Partial<Agent>): any {
    const payload: any = {};
    if (agent.code !== undefined) payload.code = agent.code;
    if (agent.profile !== undefined) payload.profile = agent.profile;
    if (agent.principalCode !== undefined) payload.principalCode = agent.principalCode;
    if (agent.caisseProfile !== undefined) payload.caisseProfile = agent.caisseProfile;
    if (agent.statut !== undefined) payload.statut = agent.statut;
    if (agent.zone !== undefined) payload.zone = agent.zone;
    if (agent.kiosk !== undefined) payload.kiosk = agent.kiosk;
    if (agent.fonction !== undefined) payload.fonction = agent.fonction;
    if (agent.dateEmbauche !== undefined)
      payload.dateEmbauche = this.formatDateForApi(agent.dateEmbauche);
    if (agent.nom !== undefined) payload.nom = agent.nom;
    if (agent.prenom !== undefined) payload.prenom = agent.prenom;
    if (agent.autresNoms !== undefined) payload.autresNoms = agent.autresNoms;
    if (agent.dateNaissance !== undefined)
      payload.dateNaissance = this.formatDateForApi(agent.dateNaissance);
    if (agent.lieuNaissance !== undefined) payload.lieuNaissance = agent.lieuNaissance;
    if (agent.ville !== undefined) payload.ville = agent.ville;
    if (agent.adresse !== undefined) payload.adresse = agent.adresse;
    if (agent.autoriserAides !== undefined) payload.autoriserAides = agent.autoriserAides;
    if (agent.phone !== undefined) payload.phone = agent.phone;
    if (agent.pin !== undefined) payload.pin = agent.pin;
    if (agent.limiteInferieure !== undefined) payload.limiteInferieure = agent.limiteInferieure;
    if (agent.limiteSuperieure !== undefined) payload.limiteSuperieure = agent.limiteSuperieure;
    if (agent.limiteParTransaction !== undefined)
      payload.limiteParTransaction = agent.limiteParTransaction;
    if (agent.limiteMinAirtime !== undefined) payload.limiteMinAirtime = agent.limiteMinAirtime;
    if (agent.limiteMaxAirtime !== undefined) payload.limiteMaxAirtime = agent.limiteMaxAirtime;
    if (agent.maxPeripheriques !== undefined) payload.maxPeripheriques = agent.maxPeripheriques;
    if (agent.limitId !== undefined)
      payload.limitId = agent.limitId ? Number(agent.limitId) : undefined;
    if (agent.nationalite !== undefined) payload.nationalite = agent.nationalite;
    if (agent.cni !== undefined) payload.cni = agent.cni;
    if (agent.cniDelivreeLe !== undefined)
      payload.cniDelivreeLe = this.formatDateForApi(agent.cniDelivreeLe);
    if (agent.cniDelivreeA !== undefined) payload.cniDelivreeA = agent.cniDelivreeA;
    if (agent.residence !== undefined) payload.residence = agent.residence;
    if (agent.autreAdresse1 !== undefined) payload.autreAdresse1 = agent.autreAdresse1;
    if (agent.statutMarital !== undefined) payload.statutMarital = agent.statutMarital;
    if (agent.epoux !== undefined) payload.epoux = agent.epoux;
    if (agent.autreTelephone !== undefined) payload.autreTelephone = agent.autreTelephone;
    if (agent.createdBy !== undefined) payload.createdBy = agent.createdBy;
    // Include tpes if provided - transform to API format
    if (agent.tpes !== undefined) {
      payload.tpes = agent.tpes.map((tpe) => ({
        id: tpe.id ? Number(tpe.id) : undefined,
        imei: tpe.imei,
        serial: tpe.serial,
        type: tpe.type,
        marque: tpe.marque,
        modele: tpe.modele,
        statut: tpe.statut,
        agent: undefined, // Will be set by backend
        assigne: tpe.assigne,
        createdAt: tpe.createdAt,
        updatedAt: tpe.updatedAt,
      }));
    }
    return payload;
  }

  // GET /api/v1/agents/{id} - Get by ID
  getById(id: string): Observable<Agent | undefined> {
    if (USE_SERVER) {
      return this.http
        .get<AgentApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
        .pipe(
          map((apiAgent) => this.transformAgent(apiAgent)),
          catchError((err) => {
            console.error(`Error fetching agent ${id}:`, err);
            return of(undefined);
          })
        );
    }
    return of(undefined);
  }

  // GET /api/v1/agents - List all
  list(params?: ListParams): Observable<PagedResult<Agent>> {
    let httpParams = new HttpParams();
    if (params) {
      if (params.page) httpParams = httpParams.set('page', params.page.toString());
      if (params.size) httpParams = httpParams.set('perPage', params.size.toString());
      if (params.search) httpParams = httpParams.set('search', params.search);
      if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
      if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
    }

    return this.http
      .get<PagedResult<AgentApiResponse>>(this.apiUrl, {
        params: httpParams,
        headers: this.getNgrokHeaders(),
      })
      .pipe(
        map((res) => {
          const agents = res.content.map((apiAgent) => {
            const transformed = this.transformAgent(apiAgent);
            return transformed;
          });
          // If pagination params provided, return paginated result
          const resAgent = {
            ...res,
            content: agents
          }
          // Otherwise return all as single page
          return resAgent;
        }),
        catchError((err) => {
          console.error('Error fetching agents:', err);
          return of(normalizePage<Agent>({ content: [], meta: { total: 0 } }, 1, 10));
        })
      );
  }

  // POST /api/v1/agents - Create
  create(payload: Omit<Agent, 'id' | 'createdAt' | 'updatedAt'>): Observable<Agent> {
    if (USE_SERVER) {
      const apiPayload = this.transformToApiPayload(payload);
      return this.http
        .post<AgentApiResponse>(this.apiUrl, apiPayload, { headers: this.getNgrokHeaders() })
        .pipe(
          map((apiAgent) => this.transformAgent(apiAgent)),
          catchError((err) => {
            console.error('Error creating agent:', err);
            throw err;
          })
        );
    }
    throw new Error('Server mode is required');
  }

  // PUT /api/v1/agents/{id} - Update
  update(id: string, payload: Partial<Agent>): Observable<Agent | undefined> {
    if (USE_SERVER) {
      const apiPayload = this.transformToApiPayload(payload);
      return this.http
        .put<AgentApiResponse>(`${this.apiUrl}/${id}`, apiPayload, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((apiAgent) => this.transformAgent(apiAgent)),
          catchError((err) => {
            console.error(`Error updating agent ${id}:`, err);
            return of(undefined);
          })
        );
    }
    return of(undefined);
  }

  // DELETE /api/v1/agents/{id} - Delete
  delete(id: string): Observable<boolean> {
    if (USE_SERVER) {
      return this.http
        .delete<void>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
        .pipe(
          map(() => true),
          catchError((err) => {
            console.error(`Error deleting agent ${id}:`, err);
            return of(false);
          })
        );
    }
    return of(false);
  }

  // GET /api/v1/agents/ville/{ville} - List by ville
  getByVille(ville: string): Observable<Agent[]> {
    if (USE_SERVER) {
      return this.http
        .get<AgentApiResponse[]>(`${this.apiUrl}/ville/${ville}`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((list) => list.map((apiAgent) => this.transformAgent(apiAgent))),
          catchError((err) => {
            console.error(`Error fetching agents by ville ${ville}:`, err);
            return of([]);
          })
        );
    }
    return of([]);
  }

  // GET /api/v1/agents/statut/{statut} - List by statut
  getByStatut(statut: AgentStatus): Observable<Agent[]> {
    if (USE_SERVER) {
      return this.http
        .get<AgentApiResponse[]>(`${this.apiUrl}/statut/${statut}`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((list) => list.map((apiAgent) => this.transformAgent(apiAgent))),
          catchError((err) => {
            console.error(`Error fetching agents by statut ${statut}:`, err);
            return of([]);
          })
        );
    }
    return of([]);
  }

  // GET /api/v1/agents/search - Search by nom or prenom
  search(query: string): Observable<Agent[]> {
    if (USE_SERVER) {
      return this.http
        .get<AgentApiResponse[]>(`${this.apiUrl}/search`, {
          params: { q: query.trim() },
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((list) => list.map((apiAgent) => this.transformAgent(apiAgent))),
          catchError((err) => {
            console.error(`Error searching agents with query ${query}:`, err);
            return of([]);
          })
        );
    }
    return of([]);
  }

  // GET /api/v1/agents/code/{code} - Get by code
  getByCode(code: string): Observable<Agent | undefined> {
    if (USE_SERVER) {
      return this.http
        .get<AgentApiResponse>(`${this.apiUrl}/code/${code}`, { headers: this.getNgrokHeaders() })
        .pipe(
          map((apiAgent) => this.transformAgent(apiAgent)),
          catchError((err) => {
            console.error(`Error fetching agent by code ${code}:`, err);
            return of(undefined);
          })
        );
    }
    return of(undefined);
  }

  // Helper method to update all agents' limitId to a new default limit
  // This is used when a limit is set as default
  updateAllAgentsLimitId(limitId: string): Observable<boolean> {
    if (USE_SERVER) {
      // Get all agents first
      return this.list({
        page: 1,
        size: 10000,
        search: '',
        sortKey: 'code',
        sortDir: 'asc',
      } as any).pipe(
        switchMap((result) => {
          const agents = result.content;
          if (agents.length === 0) {
            return of(true);
          }
          // Update each agent's limitId in parallel
          const updateObservables = agents.map((agent) =>
            this.update(agent.id, { limitId }).pipe(
              catchError((err) => {
                console.error(`Error updating agent ${agent.id} limitId:`, err);
                return of(undefined);
              })
            )
          );
          // Wait for all updates to complete
          return forkJoin(updateObservables).pipe(
            map(() => true),
            catchError((err) => {
              console.error('Error updating all agents limitId:', err);
              return of(false);
            })
          );
        }),
        catchError((err) => {
          console.error('Error fetching agents for limitId update:', err);
          return of(false);
        })
      );
    }
    return of(false);
  }
}
