/**
 * API Client for SchoolCompare FastAPI Backend
 * Handles all data fetching from the API with proper error handling and caching
 */

import type {
  SchoolsResponse,
  SchoolDetailsResponse,
  ComparisonResponse,
  RankingsResponse,
  FiltersResponse,
  MetricsResponse,
  DataInfoResponse,
  SchoolSearchParams,
  RankingsParams,
  APIError,
  LAaveragesResponse,
  NationalAverages,
} from './types';

// ============================================================================
// Configuration
// ============================================================================

// Use FASTAPI_URL for server-side requests (internal Docker network)
// Use NEXT_PUBLIC_API_URL for client-side requests (browser)
const API_BASE_URL = typeof window === 'undefined'
  ? (process.env.FASTAPI_URL || process.env.NEXT_PUBLIC_API_URL || '/api')
  : (process.env.NEXT_PUBLIC_API_URL || '/api');

// Cache configuration for server-side fetching (Next.js revalidate)
export const CACHE_DURATION = {
  FILTERS: 3600, // 1 hour
  METRICS: 3600, // 1 hour
  SCHOOLS_LIST: 60, // 1 minute
  SCHOOL_DETAILS: 300, // 5 minutes
  RANKINGS: 300, // 5 minutes
  COMPARISON: 60, // 1 minute
  DATA_INFO: 600, // 10 minutes
};

// ============================================================================
// Error Handling
// ============================================================================

export class APIFetchError extends Error {
  constructor(
    message: string,
    public status?: number,
    public detail?: string
  ) {
    super(message);
    this.name = 'APIFetchError';
  }
}

async function handleResponse<T>(response: Response): Promise<T> {
  if (!response.ok) {
    let errorDetail = `HTTP ${response.status}: ${response.statusText}`;

    try {
      const errorData: APIError = await response.json();
      errorDetail = errorData.detail || errorDetail;
    } catch {
      // If parsing JSON fails, use the default error
    }

    // Client-side: report to analytics so we can spot silent failures.
    // No-ops on SSR (track guards against missing window).
    if (typeof window !== 'undefined') {
      const { track } = await import('./analytics');
      try {
        const endpoint = new URL(response.url).pathname;
        track('api_error', { endpoint, status: response.status, route: window.location.pathname });
      } catch { /* never */ }
    }

    throw new APIFetchError(
      `API request failed: ${errorDetail}`,
      response.status,
      errorDetail
    );
  }

  return response.json();
}

// ============================================================================
// Helper Functions
// ============================================================================

function buildQueryString(params: Record<string, any>): string {
  const searchParams = new URLSearchParams();

  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && value !== null && value !== '') {
      searchParams.append(key, String(value));
    }
  });

  return searchParams.toString();
}

// ============================================================================
// School APIs
// ============================================================================

/**
 * Fetch schools with optional search and filters
 * Supports both server-side (SSR) and client-side fetching
 */
export async function fetchSchools(
  params: SchoolSearchParams = {},
  options: RequestInit = {}
): Promise<SchoolsResponse> {
  const queryString = buildQueryString(params);
  const url = `${API_BASE_URL}/schools${queryString ? `?${queryString}` : ''}`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.SCHOOLS_LIST,
      ...options.next,
    },
  });

  return handleResponse<SchoolsResponse>(response);
}

/**
 * Fetch detailed information for a specific school by URN
 */
export async function fetchSchoolDetails(
  urn: number,
  options: RequestInit = {}
): Promise<SchoolDetailsResponse> {
  const url = `${API_BASE_URL}/schools/${urn}`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.SCHOOL_DETAILS,
      ...options.next,
    },
  });

  return handleResponse<SchoolDetailsResponse>(response);
}

// ============================================================================
// Comparison APIs
// ============================================================================

/**
 * Fetch comparison data for multiple schools
 * @param urns - Comma-separated URNs or array of URNs
 */
export async function fetchComparison(
  urns: string | number[],
  options: RequestInit = {}
): Promise<ComparisonResponse> {
  const urnsString = Array.isArray(urns) ? urns.join(',') : urns;
  const url = `${API_BASE_URL}/compare?urns=${urnsString}`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.COMPARISON,
      ...options.next,
    },
  });

  return handleResponse<ComparisonResponse>(response);
}

// ============================================================================
// Rankings APIs
// ============================================================================

/**
 * Fetch school rankings by metric
 */
export async function fetchRankings(
  params: RankingsParams,
  options: RequestInit = {}
): Promise<RankingsResponse> {
  const queryString = buildQueryString(params);
  const url = `${API_BASE_URL}/rankings?${queryString}`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.RANKINGS,
      ...options.next,
    },
  });

  return handleResponse<RankingsResponse>(response);
}

// ============================================================================
// Filter & Metadata APIs
// ============================================================================

/**
 * Fetch available filter options (local authorities, school types, years)
 */
export async function fetchFilters(
  options: RequestInit = {}
): Promise<FiltersResponse> {
  const url = `${API_BASE_URL}/filters`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.FILTERS,
      ...options.next,
    },
  });

  return handleResponse<FiltersResponse>(response);
}

/**
 * Fetch metric definitions (labels, descriptions, formats)
 */
export async function fetchMetrics(
  options: RequestInit = {}
): Promise<MetricsResponse> {
  const url = `${API_BASE_URL}/metrics`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.METRICS,
      ...options.next,
    },
  });

  const data = await handleResponse<any>(response);

  // Transform backend response to match our TypeScript types
  // Backend uses 'name' and 'type', we use 'label' and 'format'
  return {
    metrics: data.metrics.map((metric: any) => ({
      key: metric.key,
      label: metric.name, // Map 'name' to 'label'
      description: metric.description,
      category: metric.category,
      format: metric.type, // Map 'type' to 'format'
      hasNationalAverage: metric.hasNationalAverage,
    })),
  };
}

/**
 * Fetch per-LA average Attainment 8 score for secondary schools
 */
export async function fetchLAaverages(
  options: RequestInit = {}
): Promise<LAaveragesResponse> {
  const url = `${API_BASE_URL}/la-averages`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: 3600,
      ...options.next,
    },
  });

  return handleResponse<LAaveragesResponse>(response);
}

/**
 * Fetch official DfE KS2 national averages (primary) and computed KS4 secondary averages.
 * Returns latest year snapshot plus per-year history for chart reference lines.
 */
export async function fetchNationalAverages(
  options: RequestInit = {}
): Promise<NationalAverages> {
  const url = `${API_BASE_URL}/national-averages`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: 3600,
      ...options.next,
    },
  });

  return handleResponse<NationalAverages>(response);
}

/**
 * Fetch database statistics and info
 */
export async function fetchDataInfo(
  options: RequestInit = {}
): Promise<DataInfoResponse> {
  const url = `${API_BASE_URL}/data-info`;

  const response = await fetch(url, {
    ...options,
    next: {
      revalidate: CACHE_DURATION.DATA_INFO,
      ...options.next,
    },
  });

  return handleResponse<DataInfoResponse>(response);
}

// ============================================================================
// Client-Side Fetcher (for SWR)
// ============================================================================

/**
 * Generic fetcher function for use with SWR
 * @example
 * ```tsx
 * const { data, error } = useSWR('/api/schools', fetcher);
 * ```
 */
export async function fetcher<T>(url: string): Promise<T> {
  // If it's already a full URL, use it directly
  // Otherwise, prepend the API_BASE_URL
  const fullUrl = url.startsWith('http') ? url : `${API_BASE_URL}${url.startsWith('/') ? url : `/${url}`}`;

  const response = await fetch(fullUrl);
  return handleResponse<T>(response);
}

// ============================================================================
// Geocoding API
// ============================================================================

/**
 * Geocode a UK postcode using postcodes.io
 */
export async function geocodePostcode(postcode: string): Promise<{
  latitude: number;
  longitude: number;
} | null> {
  try {
    const cleanPostcode = postcode.trim().toUpperCase();
    const response = await fetch(
      `https://api.postcodes.io/postcodes/${encodeURIComponent(cleanPostcode)}`
    );

    if (!response.ok) {
      return null;
    }

    const data = await response.json();

    if (data.result) {
      return {
        latitude: data.result.latitude,
        longitude: data.result.longitude,
      };
    }

    return null;
  } catch (error) {
    console.error('Geocoding error:', error);
    return null;
  }
}

// ============================================================================
// Utility Functions
// ============================================================================

/**
 * Calculate distance between two coordinates using Haversine formula
 * @returns Distance in kilometers
 */
export function calculateDistance(
  lat1: number,
  lon1: number,
  lat2: number,
  lon2: number
): number {
  const R = 6371; // Earth's radius in kilometers
  const dLat = ((lat2 - lat1) * Math.PI) / 180;
  const dLon = ((lon2 - lon1) * Math.PI) / 180;

  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos((lat1 * Math.PI) / 180) *
      Math.cos((lat2 * Math.PI) / 180) *
      Math.sin(dLon / 2) *
      Math.sin(dLon / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

/**
 * Convert kilometers to miles
 */
export function kmToMiles(km: number): number {
  return km * 0.621371;
}
