import { Injectable, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, forkJoin } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { Reunion } from '../interfaces/reunion';
import { Hippodrome } from '../interfaces/hippodrome';
import { normalizePage } from '@shared/paging/normalize-page';
import { PaginatedHttpService } from '@shared/paging/paginated-http.service';
import { ListParams, PagedResult } from '@shared/paging/paging';
import { environment } from 'src/environments/environment.development';
import { HippodromeService } from './hippodrome';

// API response interface (has hippodromeId instead of hippodrome)
interface ReunionApiResponse {
  id: string;
  code: string;
  nom: string;
  date: string;
  numero: number;
  statut: string;
  hippodromeId: string;
  totalCourses?: number;
  createdAt: string;
  updatedAt: string;
}

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

@Injectable({ providedIn: 'root' })
export class ReunionService {
  private apiUrl = environment.apiBaseUrl + API_BASE;
  private store = signal<Reunion[]>([]);

  constructor(
    private http: HttpClient,
    private paginatedHttp: PaginatedHttpService,
    private hippodromeService: HippodromeService
  ) {}

  // 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' } : {};
  }

  list(
    params: ListParams,
    usePaginationEndpoint: boolean = true
  ): Observable<PagedResult<Reunion>> {
    if (USE_SERVER) {
      if (usePaginationEndpoint) {
        return this.paginatedHttp
          .fetch<ReunionApiResponse>(this.apiUrl, params, {
            zeroBasedPageIndex: false,
        buildSort: (key, dir) => (key && dir ? ['sort', `${key},${dir}`] : null),
          })
          .pipe(
            switchMap((pagedResult) => {
              // Handle empty data case
              if (!pagedResult.content || pagedResult.content.length === 0) {
                return of({
                  ...pagedResult,
                  content: [],
                  pageable: {
                    ...pagedResult.pageable,
                  },
                });
              }

              // Extract unique hippodrome IDs from the paginated data
              const uniqueHippodromeIds = [
                ...new Set(pagedResult.content.map((r) => String(r.hippodromeId))),
              ];

              // Handle case where there are no unique IDs
              if (uniqueHippodromeIds.length === 0) {
                return of({
                  ...pagedResult,
                  content: [],
                  pageable: {
                    ...pagedResult.pageable,
                  },
                });
              }

              // Fetch all unique hippodromes in parallel
              const hippodromeRequests = uniqueHippodromeIds.map((id) =>
                this.hippodromeService
                  .getById(id)
                  .pipe(catchError(() => of<Hippodrome>()))
              );

              // Fetch courses to calculate counts per reunion
              const coursesRequest = this.http
                .get<any[]>(`${environment.apiBaseUrl}/api/courses`, {
                  headers: this.getNgrokHeaders(),
                })
                .pipe(
                  catchError(() => of([])),
                  map((data) => data || [])
                );

              return forkJoin({
                hippodromes: forkJoin(hippodromeRequests),
                courses: coursesRequest,
              }).pipe(
                map(({ hippodromes, courses }) => {
                  // Create a map of hippodrome ID to hippodrome object
                  const hippodromeMap = new Map<string, Hippodrome>();
                  uniqueHippodromeIds.forEach((id, index) => {
                    const hippodrome = hippodromes[index];
                    if (hippodrome) {
                      hippodromeMap.set(id, hippodrome);
                    }
                  });

                  // Count courses per reunion
                  const courseCountMap = new Map<string, number>();
                  courses.forEach((course: any) => {
                    const reunionId = String(course.reunionId || course.reunion?.id);
                    if (reunionId && reunionId !== 'undefined' && reunionId !== 'null') {
                      courseCountMap.set(
                        reunionId,
                        (courseCountMap.get(reunionId) || 0) + 1
                      );
                    }
                  });

                  // Transform API responses to Reunion objects
                  const transformedData: Reunion[] = pagedResult.content
                    .map((apiReunion) => {
                      const hippodrome = hippodromeMap.get(String(apiReunion.hippodromeId));
                      if (!hippodrome) {
                        return null;
                      }
                      const reunionId = String(apiReunion.id);
                      const courseCount = courseCountMap.get(reunionId) ?? apiReunion.totalCourses ?? 0;
                      return {
                        id: reunionId,
                        code: apiReunion.code,
                        nom: apiReunion.nom,
                        date: apiReunion.date,
                        numero: apiReunion.numero,
                        statut: apiReunion.statut as any,
                        hippodrome,
                        totalCourses: courseCount,
                        createdAt: apiReunion.createdAt,
                        updatedAt: apiReunion.updatedAt,
                      } as Reunion;
                    })
                    .filter((r): r is Reunion => r !== null && r !== undefined);
                    return {
                      ...pagedResult,
                      content: transformedData,
                    }
                })
              );
            }),
            catchError((err) => {
              console.error('Error fetching reunions:', err);
              return this.getMockList(params);
            })
          );
      } else {
        // Fetch all data and apply client-side pagination
        return this.http
          .get<ReunionApiResponse[]>(this.apiUrl, { headers: this.getNgrokHeaders() })
          .pipe(
            switchMap((apiData) => {
              // Handle empty data case
              if (!apiData || apiData.length === 0) {
                return of(
                  normalizePage<Reunion>(
                    {
                      data: [],
                      meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
                    },
                    params.page,
                    params.size
                  )
                );
              }

              // Extract unique hippodrome IDs
              const uniqueHippodromeIds = [...new Set(apiData.map((r) => String(r.hippodromeId)))];

              // Handle case where there are no unique IDs (shouldn't happen, but be safe)
              if (uniqueHippodromeIds.length === 0) {
                return of(
                  normalizePage<Reunion>(
                    {
                      data: [],
                      meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
                    },
                    params.page,
                    params.size
                  )
                );
              }

              // Fetch all unique hippodromes and all courses in parallel
              const hippodromeRequests = uniqueHippodromeIds.map((id) =>
                this.hippodromeService
                  .getById(id)
                  .pipe(catchError(() => of<Hippodrome | undefined>(undefined)))
              );

              // Fetch courses to calculate counts per reunion
              const coursesRequest = this.http
                .get<any[]>(`${environment.apiBaseUrl}/api/v1/courses`, {
                  headers: this.getNgrokHeaders(),
                })
                .pipe(
                  catchError(() => of([])),
                  map((data) => data || [])
                );

              return forkJoin({
                hippodromes: forkJoin(hippodromeRequests),
                courses: coursesRequest,
              }).pipe(
                map(({ hippodromes, courses }) => {
                  // Create a map of hippodrome ID to hippodrome object
                  const hippodromeMap = new Map<string, Hippodrome>();
                  uniqueHippodromeIds.forEach((id, index) => {
                    const hippodrome = hippodromes[index];
                    if (hippodrome) {
                      hippodromeMap.set(id, hippodrome);
                    }
                  });

                  // Count courses per reunion
                  const courseCountMap = new Map<string, number>();
                  courses.forEach((course: any) => {
                    const reunionId = String(course.reunionId || course.reunion?.id);
                    if (reunionId && reunionId !== 'undefined' && reunionId !== 'null') {
                      courseCountMap.set(
                        reunionId,
                        (courseCountMap.get(reunionId) || 0) + 1
                      );
                    }
                  });

                  // Transform API responses to Reunion objects
                  const transformedData: Reunion[] = apiData
                    .map((apiReunion) => {
                      const hippodrome = hippodromeMap.get(String(apiReunion.hippodromeId));
                      if (!hippodrome) {
                        // Skip if hippodrome not found
                        return null;
                      }
                      const reunionId = String(apiReunion.id);
                      const courseCount = courseCountMap.get(reunionId) ?? apiReunion.totalCourses ?? 0;
                      return {
                        id: reunionId,
                        code: apiReunion.code,
                        nom: apiReunion.nom,
                        date: apiReunion.date,
                        numero: apiReunion.numero,
                        statut: apiReunion.statut as any,
                        hippodrome,
                        totalCourses: courseCount,
                        createdAt: apiReunion.createdAt,
                        updatedAt: apiReunion.updatedAt,
                      } as Reunion;
                    })
                    .filter((r): r is Reunion => r !== null && r !== undefined);

                  // Apply client-side filtering, sorting, and pagination
                  let filtered = this.applyClientFilters(transformedData, params);
                  const total = filtered.length;
                  const start = (params.page - 1) * params.size;
                  const pageData = filtered.slice(start, start + params.size);

                  const upcomingReunions = filtered.filter(
                    (r) => new Date(r.date) >= new Date()
                  ).length;
                  const pastReunions = filtered.filter((r) => new Date(r.date) < new Date()).length;
                  const uniqueHippodromes = new Set(filtered.map((r) => r.hippodrome.id)).size;

                  return normalizePage<Reunion>(
                    {
                      data: pageData,
                      meta: { total, uniqueHippodromes, upcomingReunions, pastReunions },
                    },
                    params.page,
                    params.size
                  );
                })
              );
            }),
            catchError((err) => {
              console.error('Error fetching reunions:', err);
              return this.getMockList(params);
            })
          );
      }
    }

    return this.getMockList(params);
  }

  private applyClientFilters(data: Reunion[], params: ListParams): Reunion[] {
    let filtered = [...data];

    // Search filter
    const q = (params.search ?? '').toLowerCase();
    if (q) {
      filtered = filtered.filter(
        (r) =>
          r.nom.toLowerCase().includes(q) ||
          r.hippodrome.nom.toLowerCase().includes(q) ||
          r.hippodrome.ville.toLowerCase().includes(q)
      );
    }

    // Sort
    if (params.sortKey && params.sortDir) {
      const { sortKey, sortDir } = params;
      filtered.sort((a: any, b: any) => {
        const va = a[sortKey!],
          vb = b[sortKey!];
        const sa = va == null ? '' : String(va);
        const sb = vb == null ? '' : String(vb);
        const cmp = sa.localeCompare(sb, 'fr', { numeric: true, sensitivity: 'base' });
        return sortDir === 'asc' ? cmp : -cmp;
      });
    }

    return filtered;
  }

  private getMockList(params: ListParams): Observable<PagedResult<Reunion>> {
    const q = (params.search ?? '').toLowerCase();
    let data = this.store();

    if (q) {
      data = data.filter(
        (r) =>
          r.nom.toLowerCase().includes(q) ||
          r.hippodrome.nom.toLowerCase().includes(q) ||
          r.hippodrome.ville.toLowerCase().includes(q)
      );
    }

    if (params.sortKey && params.sortDir) {
      const { sortKey, sortDir } = params;
      data = [...data].sort((a: any, b: any) => {
        const va = a[sortKey!],
          vb = b[sortKey!];
        const sa = va == null ? '' : String(va);
        const sb = vb == null ? '' : String(vb);
        const cmp = sa.localeCompare(sb, 'fr', { numeric: true, sensitivity: 'base' });
        return sortDir === 'asc' ? cmp : -cmp;
      });
    }

    const start = (params.page - 1) * params.size;
    const pageData = data.slice(start, start + params.size);

    const upcomingReunions = data.filter((r) => new Date(r.date) >= new Date()).length;
    const pastReunions = data.filter((r) => new Date(r.date) < new Date()).length;
    const uniqueHippodromes = new Set(data.map((r) => r.hippodrome.nom)).size;

    return of(
      normalizePage<Reunion>(
        {
          data: pageData,
          meta: { total: data.length, uniqueHippodromes, upcomingReunions, pastReunions },
        },
        params.page,
        params.size
      )
    );
  }

  getById(id: string): Observable<Reunion | undefined> {
    if (USE_SERVER) {
      return this.http
        .get<ReunionApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
        .pipe(
          switchMap((apiReunion) => {
            // Fetch the hippodrome data
            return this.hippodromeService.getById(String(apiReunion.hippodromeId)).pipe(
              map((hippodrome) => {
                if (!hippodrome) {
                  return undefined;
                }
                return {
                  id: String(apiReunion.id),
                  code: apiReunion.code,
                  nom: apiReunion.nom,
                  date: apiReunion.date,
                  numero: apiReunion.numero,
                  statut: apiReunion.statut as any,
                  hippodrome,
                  totalCourses: apiReunion.totalCourses,
                  createdAt: apiReunion.createdAt,
                  updatedAt: apiReunion.updatedAt,
                } as Reunion;
              })
            );
          }),
          catchError((err) => {
            console.error(`Error fetching reunion ${id}:`, err);
            return of(this.store().find((r) => r.id === id));
          })
        );
    }
    const found = this.store().find((r) => r.id === id);
    return of(found);
  }

  getByCode(code: string): Observable<Reunion | undefined> {
    if (USE_SERVER) {
      return this.http
        .get<ReunionApiResponse>(`${this.apiUrl}/code/${encodeURIComponent(code)}`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          switchMap((apiReunion) => {
            // Fetch the hippodrome data
            return this.hippodromeService.getById(String(apiReunion.hippodromeId)).pipe(
              map((hippodrome) => {
                if (!hippodrome) {
                  return undefined;
                }
                return {
                  id: String(apiReunion.id),
                  code: apiReunion.code,
                  nom: apiReunion.nom,
                  date: apiReunion.date,
                  numero: apiReunion.numero,
                  statut: apiReunion.statut as any,
                  hippodrome,
                  totalCourses: apiReunion.totalCourses,
                  createdAt: apiReunion.createdAt,
                  updatedAt: apiReunion.updatedAt,
                } as Reunion;
              })
            );
          }),
          catchError((err) => {
            console.error(`Error fetching reunion by code ${code}:`, err);
            return of(this.store().find((r) => r.code === code));
          })
        );
    }
    return of(this.store().find((r) => r.code === code));
  }

  create(payload: Omit<Reunion, 'id'>): Observable<Reunion> {
    if (USE_SERVER) {
      return this.http
        .post<Reunion>(this.apiUrl, payload, { headers: this.getNgrokHeaders() })
        .pipe(
          catchError((err) => {
            console.error('Error creating reunion:', err);
            const item: Reunion = { id: crypto.randomUUID(), ...payload };
            this.store.set([item, ...this.store()]);
            return of(item);
          })
        );
    }
    const item: Reunion = { id: crypto.randomUUID(), ...payload };
    this.store.set([item, ...this.store()]);
    return of(item);
  }

  update(id: string, payload: Partial<Reunion>): Observable<Reunion | undefined> {
    if (USE_SERVER) {
      return this.http
        .put<Reunion>(`${this.apiUrl}/${id}`, payload, { headers: this.getNgrokHeaders() })
        .pipe(
          catchError((err) => {
            console.error(`Error updating reunion ${id}:`, err);
            let updated: Reunion | undefined;
            this.store.set(
              this.store().map((r) => {
                if (r.id === id) {
                  updated = { ...r, ...payload };
                  return updated;
                }
                return r;
              })
            );
            return of(updated);
          })
        );
    }
    let updated: Reunion | undefined;
    this.store.set(
      this.store().map((r) => {
        if (r.id === id) {
          updated = { ...r, ...payload };
          return updated;
        }
        return r;
      })
    );
    return of(updated);
  }

  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 reunion ${id}:`, err);
            const before = this.store().length;
            this.store.set(this.store().filter((r) => r.id !== id));
            return of(this.store().length < before);
          })
        );
    }
    const before = this.store().length;
    this.store.set(this.store().filter((r) => r.id !== id));
    return of(this.store().length < before);
  }
}
