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 { 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';

const USE_SERVER = true;
const API_BASE = '/api/v1/hippodromes';

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

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

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

  // LISTE — supporte client & serveur
  list(
    params: ListParams,
    usePaginationEndpoint: boolean = false
  ): Observable<PagedResult<Hippodrome>> {
    if (USE_SERVER) {
      // If there's a search query, use the search endpoint
      if (params.search && params.search.trim()) {
        return this.search(params.search.trim()).pipe(
          switchMap((hippodromes) => {
            // Fetch all reunions and courses to calculate counts
            return forkJoin({
              reunions: this.http
                .get<any[]>(`${environment.apiBaseUrl}/api/v1/reunions`, {
                  headers: this.getNgrokHeaders(),
                })
                .pipe(
                  catchError(() => of([])),
                  map((data) => ({ data: data || [], meta: { total: (data || []).length } }))
                ),
              courses: this.http
                .get<any[]>(`${environment.apiBaseUrl}/api/v1/courses`, {
                  headers: this.getNgrokHeaders(),
                })
                .pipe(
                  catchError(() => of([])),
                  map((data) => ({ data: data || [], meta: { total: (data || []).length } }))
                ),
            }).pipe(
              map(({ reunions, courses }) => {
                // Count reunions per hippodrome
                const reunionCountMap = new Map<string, number>();
                reunions.data.forEach((reunion: any) => {
                  const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
                  if (hippodromeId && hippodromeId !== 'undefined' && hippodromeId !== 'null') {
                    reunionCountMap.set(hippodromeId, (reunionCountMap.get(hippodromeId) || 0) + 1);
                  }
                });

                // Create a map of reunionId -> hippodromeId from reunions
                const reunionToHippodromeMap = new Map<string, string>();
                reunions.data.forEach((reunion: any) => {
                  const reunionId = String(reunion.id);
                  const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
                  if (
                    reunionId &&
                    reunionId !== 'undefined' &&
                    reunionId !== 'null' &&
                    hippodromeId &&
                    hippodromeId !== 'undefined' &&
                    hippodromeId !== 'null'
                  ) {
                    reunionToHippodromeMap.set(reunionId, hippodromeId);
                  }
                });

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

                // Add counts to hippodromes
                const hippodromesWithCounts = hippodromes.map((h) => ({
                  ...h,
                  reunionCount: reunionCountMap.get(String(h.id)) ?? 0,
                  courseCount: courseCountMap.get(String(h.id)) ?? 0,
                }));

                // Apply client-side sorting and pagination
                let filtered = this.applyClientFilters(hippodromesWithCounts, {
                  ...params,
                  search: '', // Already filtered by search endpoint
                });
                const total = filtered.length;
                const start = (params.page - 1) * params.perPage;
                const pageData = filtered.slice(start, start + params.perPage);

                const uniqueCountries = new Set(filtered.map((h) => h.pays)).size;
                const uniqueCities = new Set(filtered.map((h) => h.ville)).size;
                const averageByCountry = filtered.length
                  ? Math.round(filtered.length / uniqueCountries)
                  : 0;
                const totalReunions = filtered.reduce((acc, h) => acc + (h.reunionCount ?? 0), 0);
                const totalCourses = filtered.reduce((acc, h) => acc + (h.courseCount ?? 0), 0);

                return normalizePage<Hippodrome>(
                  {
                    data: pageData,
                    meta: {
                      total,
                      uniqueCountries,
                      uniqueCities,
                      averageByCountry,
                      totalReunions,
                      totalCourses,
                    },
                  },
                  params.page,
                  params.perPage
                );
              })
            );
          }),
          catchError((err) => {
            console.error('Error searching hippodromes:', err);
            return of(
              normalizePage<Hippodrome>(
                {
                  data: [],
                  meta: {
                    total: 0,
                    uniqueCountries: 0,
                    uniqueCities: 0,
                    averageByCountry: 0,
                    totalReunions: 0,
                    totalCourses: 0,
                  },
                },
                params.page,
                params.perPage
              )
            );
          })
        );
      }

      if (usePaginationEndpoint) {
        return this.paginatedHttp
          .fetch<Hippodrome>(this.apiUrl, params, {
            zeroBasedPageIndex: false,
            buildSort: (key, dir) => (key && dir ? ['sort', `${key},${dir}`] : null),
            mapClientSortKey: (k) => {
              const alias: Record<string, string> = {
                name: 'nom',
                city: 'ville',
                country: 'pays',
              };
              return k ? alias[k] ?? k : undefined;
            },
          })
          .pipe(
            catchError((err) => {
              console.error('Error fetching hippodromes:', err);
              return of(
                normalizePage<Hippodrome>(
                  {
                    data: [],
                    meta: {
                      total: 0,
                      uniqueCountries: 0,
                      uniqueCities: 0,
                      averageByCountry: 0,
                      totalReunions: 0,
                      totalCourses: 0,
                    },
                  },
                  params.page,
                  params.perPage
                )
              );
            })
          );
      } else {
        // Fetch all data and apply client-side pagination
        return this.http
          .get<Hippodrome[]>(`${this.apiUrl}/actifs`, {
            headers: this.getNgrokHeaders(),
          })
          .pipe(
            switchMap((allData) => {
              // Fetch all reunions and courses directly from API to calculate counts
              // We fetch directly to avoid circular dependency with ReunionService and CourseService
              return forkJoin({
                reunions: this.http
                  .get<any[]>(`${environment.apiBaseUrl}/api/v1/reunions`, {
                    headers: this.getNgrokHeaders(),
                  })
                  .pipe(
                    catchError(() => of([])),
                    map((data) => ({ data, meta: { total: data.length } }))
                  ),
                courses: this.http
                  .get<any[]>(`${environment.apiBaseUrl}/api/v1/courses`, {
                    headers: this.getNgrokHeaders(),
                  })
                  .pipe(
                    catchError(() => of([])),
                    map((data) => ({ data, meta: { total: data.length } }))
                  ),
              }).pipe(
                map(({ reunions, courses }) => {
                  // Count reunions per hippodrome
                  const reunionCountMap = new Map<string, number>();
                  reunions.data.forEach((reunion: any) => {
                    const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
                    if (hippodromeId && hippodromeId !== 'undefined' && hippodromeId !== 'null') {
                      reunionCountMap.set(
                        hippodromeId,
                        (reunionCountMap.get(hippodromeId) || 0) + 1
                      );
                    }
                  });

                  // Create a map of reunionId -> hippodromeId from reunions
                  const reunionToHippodromeMap = new Map<string, string>();
                  reunions.data.forEach((reunion: any) => {
                    const reunionId = String(reunion.id);
                    const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
                    if (
                      reunionId &&
                      reunionId !== 'undefined' &&
                      reunionId !== 'null' &&
                      hippodromeId &&
                      hippodromeId !== 'undefined' &&
                      hippodromeId !== 'null'
                    ) {
                      reunionToHippodromeMap.set(reunionId, hippodromeId);
                    }
                  });

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

                  // Add counts to hippodromes
                  const hippodromesWithCounts = allData.map((h) => ({
                    ...h,
                    reunionCount: reunionCountMap.get(String(h.id)) ?? 0,
                    courseCount: courseCountMap.get(String(h.id)) ?? 0,
                  }));

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

                  const uniqueCountries = new Set(filtered.map((h) => h.pays)).size;
                  const uniqueCities = new Set(filtered.map((h) => h.ville)).size;
                  const averageByCountry = filtered.length
                    ? Math.round(filtered.length / uniqueCountries)
                    : 0;
                  const totalReunions = filtered.reduce((acc, h) => acc + (h.reunionCount ?? 0), 0);
                  const totalCourses = filtered.reduce((acc, h) => acc + (h.courseCount ?? 0), 0);

                  return normalizePage<Hippodrome>(
                    {
                      data: pageData,
                      meta: {
                        total,
                        uniqueCountries,
                        uniqueCities,
                        averageByCountry,
                        totalReunions,
                        totalCourses,
                      },
                    },
                    params.page,
                    params.perPage
                  );
                })
              );
            }),
            catchError((err) => {
              console.error('Error fetching hippodromes:', err);
              return of(
                normalizePage<Hippodrome>(
                  {
                    data: [],
                    meta: {
                      total: 0,
                      uniqueCountries: 0,
                      uniqueCities: 0,
                      averageByCountry: 0,
                      totalReunions: 0,
                      totalCourses: 0,
                    },
                  },
                  params.page,
                  params.perPage
                )
              );
            })
          );
      }
    }

    // Mock mode disabled - return empty result
    return of(
      normalizePage<Hippodrome>(
        {
          data: [],
          meta: {
            total: 0,
            uniqueCountries: 0,
            uniqueCities: 0,
            averageByCountry: 0,
            totalReunions: 0,
            totalCourses: 0,
          },
        },
        params.page,
        params.perPage
      )
    );
  }

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

    // Search filter
    const q = (params.search ?? '').toLowerCase();
    if (q) {
      filtered = filtered.filter(
        (h) =>
          h.nom.toLowerCase().includes(q) ||
          h.ville.toLowerCase().includes(q) ||
          h.pays.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!];
        let cmp: number;

        if (typeof va === 'number' && typeof vb === 'number') {
          cmp = va - vb;
        } else {
          const sa = va == null ? '' : String(va);
          const sb = vb == null ? '' : String(vb);
          cmp = sa.localeCompare(sb, 'fr', { numeric: true, sensitivity: 'base' });
        }

        return sortDir === 'asc' ? cmp : -cmp;
      });
    }

    return filtered;
  }

  // READ
  getById(id: string): Observable<Hippodrome | undefined> {
    if (USE_SERVER) {
      return this.http
        .get<Hippodrome>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
        .pipe(
          catchError((err) => {
            console.error(`Error fetching hippodrome ${id}:`, err);
            return of(this.store().find((h) => h.id === id));
          })
        );
    }
    const found = this.store().find((h) => h.id === id);
    return of(found);
  }

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

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

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

  // GET by ville
  getByVille(ville: string): Observable<Hippodrome[]> {
    if (USE_SERVER) {
      return this.http
        .get<Hippodrome[]>(`${this.apiUrl}/ville/${encodeURIComponent(ville)}`, {
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          catchError((err) => {
            console.error(`Error fetching hippodromes by ville ${ville}:`, err);
            return of(this.store().filter((h) => h.ville === ville));
          })
        );
    }
    return of(this.store().filter((h) => h.ville === ville));
  }

  // SEARCH by query (q parameter)
  search(query: string): Observable<Hippodrome[]> {
    if (USE_SERVER) {
      return this.http
        .get<Hippodrome[]>(`${this.apiUrl}/search`, {
          params: { nom: query.trim() },
          headers: this.getNgrokHeaders(),
        })
        .pipe(
          catchError((err) => {
            console.error(`Error searching hippodromes with query ${query}:`, err);
            const q = query.toLowerCase();
            return of(
              this.store().filter(
                (h) =>
                  h.nom.toLowerCase().includes(q) ||
                  h.ville.toLowerCase().includes(q) ||
                  h.pays.toLowerCase().includes(q)
              )
            );
          })
        );
    }
    const q = query.toLowerCase();
    return of(
      this.store().filter(
        (h) =>
          h.nom.toLowerCase().includes(q) ||
          h.ville.toLowerCase().includes(q) ||
          h.pays.toLowerCase().includes(q)
      )
    );
  }

  // GET actifs
  getActifs(): Observable<Hippodrome[]> {
    if (USE_SERVER) {
      return this.http
        .get<Hippodrome[]>(`${this.apiUrl}/actifs`, { headers: this.getNgrokHeaders() })
        .pipe(
          catchError((err) => {
            console.error('Error fetching active hippodromes:', err);
            return of(this.store().filter((h) => h.actif));
          })
        );
    }
    return of(this.store().filter((h) => h.actif));
  }
}
