import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { TpeDevice, TpeStatus, TpeType } from '../interfaces/tpe';
import { Agent, AgentStatus } from '../interfaces/agent';
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/terminaux';

// Interface to match the API response structure for Agent (nested in TPE)
interface AgentApiResponse {
  id: number,
  code: String,
  profil: String,
  principalCode: String,
  caisseProfile: String,
  statut: AgentStatus,
  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,
  "limiteInferieure": number,
  "limiteSuperieure": number,
  "limiteParTransaction": number,
  "limiteMinAirtime": number,
  "limiteMaxAirtime": number,
  "maxPeripheriques": number,
  "limitId": String,
  "nationalite": String,
  "cni": String,
  "cniDelivreeLe": String,
  "cniDelivreeA": String,
  "residence": String,
  "autreAdresse1": String,
  "statutMarital": String,
  "epoux": String,
  "autreTelephone": String,
  "createdAt": String,
  "updatedAt": String,
  "createdBy": String,
  "terminauxIds": [
    number[]
  ]
}

// Interface to match the API response structure
interface TpeApiResponse {
  id: number,
  numeroSerie: String,
  pointDeVenteId: number,
  statut: TpeStatus,
  derniereConnexion: String,
  versionLogicielle: String,
  typeTerminal: String,
  plateforme: String,
  modeleAppareil: String,
  systemeExploitation: String,
  versionOs: String,
  adresseIp: String,
  adresseMac: String,
  agentConnecteId: number,
  derniereConnexionAgent: String,
  derniereDeconnexionAgent: String,
  journalSession: String
}

// Stats interfaces
interface CountByStatutResponse {
  [key: string]: number;
}

// Assignment stats is just a number (count of assigned TPEs)
type AssignesStatsResponse = number;

@Injectable({ providedIn: 'root' })
export class TpeService {
  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 statut to interface statut (both use uppercase now)
  private 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 interface statut to API statut (both use uppercase now, so direct return)
  private transformStatutToApi(statut: TpeStatus): string {
    return statut; // Already uppercase, no transformation needed
  }

  // Transform API Agent response to Agent (lightweight mapping)
  private transformAgent(apiAgent: AgentApiResponse): Agent {
    return {
      id: String(apiAgent.id),
      code: String((apiAgent as any).code || ''),
      profile: String((apiAgent as any).profile || ''),
      principalCode: (apiAgent as any).principalCode ? String((apiAgent as any).principalCode) : undefined,
      caisseProfile: (apiAgent as any).caisseProfile ? String((apiAgent as any).caisseProfile) : undefined,
      statut: apiAgent.statut as AgentStatus,
      zone: (apiAgent as any).zone ? String((apiAgent as any).zone) : undefined,
      kiosk: (apiAgent as any).kiosk ? String((apiAgent as any).kiosk) : undefined,
      fonction: (apiAgent as any).fonction ? String((apiAgent as any).fonction) : undefined,
      dateEmbauche: apiAgent.dateEmbauche ? String(apiAgent.dateEmbauche) : undefined,
      nom: String(apiAgent.nom || ''),
      prenom: String(apiAgent.prenom || ''),
      autresNoms: apiAgent.autresNoms ? String(apiAgent.autresNoms) : undefined,
      dateNaissance: apiAgent.dateNaissance ? String(apiAgent.dateNaissance) : undefined,
      lieuNaissance: apiAgent.lieuNaissance ? String(apiAgent.lieuNaissance) : undefined,
      ville: apiAgent.ville ? String(apiAgent.ville) : undefined,
      adresse: apiAgent.adresse ? String(apiAgent.adresse) : undefined,
      autoriserAides: Boolean(apiAgent.autoriserAides),
      phone: String(apiAgent.phone || ''),
      pin: (apiAgent as any).pin ? String((apiAgent as any).pin) : undefined,
      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 ? String(apiAgent.nationalite) : undefined,
      cni: apiAgent.cni ? String(apiAgent.cni) : undefined,
      cniDelivreeLe: apiAgent.cniDelivreeLe ? String(apiAgent.cniDelivreeLe) : undefined,
      cniDelivreeA: apiAgent.cniDelivreeA ? String(apiAgent.cniDelivreeA) : undefined,
      residence: apiAgent.residence ? String(apiAgent.residence) : undefined,
      autreAdresse1: apiAgent.autreAdresse1 ? String(apiAgent.autreAdresse1) : undefined,
      statutMarital: apiAgent.statutMarital ? String(apiAgent.statutMarital) : undefined,
      epoux: apiAgent.epoux ? String(apiAgent.epoux) : undefined,
      autreTelephone: apiAgent.autreTelephone ? String(apiAgent.autreTelephone) : undefined,
      createdAt: apiAgent.createdAt ? String(apiAgent.createdAt) : undefined,
      updatedAt: apiAgent.updatedAt ? String(apiAgent.updatedAt) : undefined,
      createdBy: apiAgent.createdBy ? String(apiAgent.createdBy) : undefined,
    };
  }

  // Transform API response to TpeDevice
  private transformTpe(apiTpe: TpeApiResponse): TpeDevice {
    // Map API-specific names to our generic interface where possible
    const serial = (apiTpe as any).numeroSerie || (apiTpe as any).serial || '';
    const imei = (apiTpe as any).imei || serial || '';
    const typeRaw = String((apiTpe as any).typeTerminal || '').toUpperCase();
    const type = typeRaw.includes('POS') ? ('POS' as TpeType) : ('OTHER' as TpeType);
    const marque = (apiTpe as any).plateforme || (apiTpe as any).marque || '';
    const modele = (apiTpe as any).modeleAppareil || (apiTpe as any).modele || '';
    const statut = this.transformStatut(String(apiTpe.statut || 'INVALIDE'));
    // Agent mapping: sometimes API returns an agent object or only an id
    let agent: Agent | undefined = undefined;
    if ((apiTpe as any).agent && typeof (apiTpe as any).agent === 'object' && (apiTpe as any).agent.id) {
      agent = this.transformAgent((apiTpe as any).agent as AgentApiResponse);
    }
    const assigne = Boolean((apiTpe as any).agentConnecteId || (apiTpe as any).assigne);

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

  // Transform TpeDevice to API payload (best-effort)
  private transformToApiPayload(tpe: Partial<TpeDevice>): any {
    const payload: any = {};
    if (tpe.imei !== undefined) payload.numeroSerie = tpe.imei;
    if (tpe.serial !== undefined) payload.numeroSerie = tpe.serial;
    if (tpe.type !== undefined) payload.typeTerminal = tpe.type;
    if (tpe.marque !== undefined) payload.plateforme = tpe.marque;
    if (tpe.modele !== undefined) payload.modeleAppareil = tpe.modele;
    if (tpe.statut !== undefined) payload.statut = this.transformStatutToApi(tpe.statut);
    if (tpe.assigne !== undefined) payload.assigne = tpe.assigne;
    return payload;
  }

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


  // GET /api/v1/tpes - List all
  list(params?: ListParams): Observable<PagedResult<TpeDevice>> {
    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<TpeApiResponse>>(this.apiUrl, {
        params: httpParams,
        headers: this.getNgrokHeaders(),
      })
      .pipe(
        map((list) => {
          const content = (list.content || []).map((api) => this.transformTpe(api));
          return { ...list, content } as PagedResult<TpeDevice>;
        }),
        catchError((err) => {
          console.error('Error fetching TPEs:', err);
          return of(normalizePage<TpeDevice>({ content: [], meta: { total: 0 } }, 1, 10));
        })
      );
}

  // POST /api/v1/tpes - Create
  create(payload: Partial<TpeDevice>): Observable<TpeDevice> {
    const apiPayload = this.transformToApiPayload(payload);
    return this.http
      .post<TpeApiResponse>(this.apiUrl, apiPayload, { headers: this.getNgrokHeaders() })
      .pipe(
        map((apiTpe) => this.transformTpe(apiTpe)),
        catchError((err) => {
          console.error('Error creating TPE:', err);
          throw err;
        })
      );
  }

// PUT /api/v1/tpes/{id} - Update
update(id: string, payload: Partial<TpeDevice>): Observable<TpeDevice | undefined> {
    const apiPayload = this.transformToApiPayload(payload);
    return this.http
      .put<TpeApiResponse>(`${this.apiUrl}/${id}`, apiPayload, {
        headers: this.getNgrokHeaders(),
      })
      .pipe(
        map((apiTpe) => this.transformTpe(apiTpe)),
        catchError((err) => {
          console.error(`Error updating TPE ${id}:`, err);
          return of(undefined);
        })
      );
}

  // DELETE /api/v1/tpes/{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 TPE ${id}:`, err);
            return of(false);
          })
        );
    }
    return of(false);
  }

  // PATCH /api/v1/tpes/{id}/statut - Update statut
  updateStatut(id: string, statut: TpeStatus): Observable<TpeDevice | undefined> {
    if (USE_SERVER) {
      return this.http
        .patch<TpeApiResponse>(
          `${this.apiUrl}/${id}/statut`,
          { statut: this.transformStatutToApi(statut) },
          { headers: this.getNgrokHeaders() }
        )
        .pipe(
          map((apiTpe) => this.transformTpe(apiTpe)),
          catchError((err) => {
            console.error(`Error updating TPE statut ${id}:`, err);
            return of(undefined);
          })
        );
    }
    return of(undefined);
  }

  // PATCH /api/v1/tpes/{id}/liberer - Liberate TPE (updates whole TPE, sets assigne to false and statut to DISPONIBLE)
  liberer(id: string): Observable<TpeDevice | undefined> {
    if (USE_SERVER) {
      // First get the current TPE data
      return this.getById(id).pipe(
        switchMap((tpe) => {
          if (!tpe) {
            return of(undefined);
          }
          // Update the whole TPE with assigne set to false and statut to DISPONIBLE
          const updatedTpe = { ...tpe, assigne: false, statut: 'DISPONIBLE' as TpeStatus };
          const apiPayload = this.transformToApiPayload(updatedTpe);
          return this.http
            .patch<TpeApiResponse>(`${this.apiUrl}/liberer/${id}`, apiPayload, {
              headers: this.getNgrokHeaders(),
            })
            .pipe(
              map((apiTpe) => this.transformTpe(apiTpe)),
              catchError((err) => {
                console.error(`Error liberating TPE ${id}:`, err);
                return of(undefined);
              })
            );
        }),
        catchError((err) => {
          console.error(`Error fetching TPE ${id} for liberation:`, err);
          return of(undefined);
        })
      );
    }
    return of(undefined);
  }

  // PATCH /api/v1/tpes/assigner - Assign TPE
  // Payload: { tpeId: number, agentId: number }
  assigner(id: string, agentId: string): Observable<TpeDevice | undefined> {
    if (USE_SERVER) {
      const payload = {
        tpeId: Number(id),
        agentId: Number(agentId),
      };
      return this.http
        .patch<TpeApiResponse>(`${this.apiUrl}/assigner`, payload, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((apiTpe) => this.transformTpe(apiTpe)),
          catchError((err) => {
            console.error(`Error assigning TPE ${id}:`, err);
            return of(undefined);
          })
        );
    }
    return of(undefined);
  }

  // GET /api/v1/tpes/statut/{statut} - List by statut
  getByStatut(statut: TpeStatus): Observable<TpeDevice[]> {
    if (USE_SERVER) {
      const apiStatut = this.transformStatutToApi(statut);
      return this.http
        .get<TpeApiResponse[]>(`${this.apiUrl}/statut/${apiStatut}`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((list) => list.map((apiTpe) => this.transformTpe(apiTpe))),
          catchError((err) => {
            console.error(`Error fetching TPEs by statut ${statut}:`, err);
            return of([]);
          })
        );
    }
    return of([]);
  }

  // GET /api/v1/tpes/stats/count-by-statut - Get count by statut
  getCountByStatut(): Observable<CountByStatutResponse> {
    if (USE_SERVER) {
      return this.http
        .get<CountByStatutResponse>(`${this.apiUrl}/stats/count-by-statut`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          catchError((err) => {
            console.error('Error fetching TPE count by statut:', err);
            return of({});
          })
        );
    }
    return of({});
  }

  // GET /api/v1/tpes/stats/assignes - Get assignment stats (returns a number)
  getAssignesStats(): Observable<number> {
    if (USE_SERVER) {
      return this.http
        .get<number>(`${this.apiUrl}/stats/assignes`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          catchError((err) => {
            console.error('Error fetching TPE assignment stats:', err);
            return of(0);
          })
        );
    }
    return of(0);
  }

  // GET /api/v1/tpes/search - Search
  search(query: string): Observable<TpeDevice[]> {
    if (USE_SERVER) {
      return this.http
        .get<TpeApiResponse[]>(`${this.apiUrl}/search`, {
          params: { q: query.trim() },
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          map((list) => list.map((apiTpe) => this.transformTpe(apiTpe))),
          catchError((err) => {
            console.error(`Error searching TPEs with query ${query}:`, err);
            return of([]);
          })
        );
    }
    return of([]);
  }

  // GET /api/v1/tpes/disponibles - List available TPEs
  getDisponibles(): Observable<TpeDevice[]> {
    if (USE_SERVER) {
      return this.http
        .get<TpeApiResponse[]>(`${this.apiUrl}/disponibles`, { headers: this.getNgrokHeaders() })
        .pipe(
          map((list) => list.map((apiTpe) => this.transformTpe(apiTpe))),
          catchError((err) => {
            console.error('Error fetching available TPEs:', err);
            return of([]);
          })
        );
    }
    return of([]);
  }
}
