import type { TNavigationMode, IOSRMLeg } from './geo-map.navigation.js';

// ─── Traffic Data Types ──────────────────────────────────────────────────────

/**
 * A segment of road with traffic flow data
 */
export interface ITrafficFlowSegment {
  /** GeoJSON LineString geometry for the segment */
  geometry: GeoJSON.LineString;
  /** Congestion level from 0 (free flow) to 1 (blocked) */
  congestion: number;
  /** Current speed in km/h */
  speed: number;
  /** Free-flow (normal) speed in km/h */
  freeFlowSpeed: number;
  /** Confidence/quality of the data (0-1) */
  confidence: number;
}

/**
 * Traffic flow data for a geographic area
 */
export interface ITrafficFlowData {
  /** Array of road segments with traffic data */
  segments: ITrafficFlowSegment[];
  /** Timestamp when the data was fetched */
  timestamp: Date;
  /** Bounding box [west, south, east, north] */
  bounds: [number, number, number, number];
}

/**
 * A route with traffic-aware duration estimates
 */
export interface ITrafficAwareRoute {
  /** Route geometry */
  geometry: GeoJSON.LineString;
  /** Total distance in meters */
  distance: number;
  /** Duration with current traffic in seconds */
  duration: number;
  /** Duration without traffic (free-flow) in seconds */
  durationWithoutTraffic: number;
  /** Overall congestion level for the route */
  congestionLevel: 'low' | 'moderate' | 'heavy' | 'severe';
  /** Route legs with turn-by-turn directions */
  legs: IOSRMLeg[];
}

/**
 * Configuration options for traffic providers
 */
export interface ITrafficProviderConfig {
  /** API key (for providers that require authentication) */
  apiKey?: string;
  /** Server URL (for self-hosted providers) */
  serverUrl?: string;
  /** Refresh interval in milliseconds */
  refreshInterval?: number;
  /** Whether to include traffic incidents */
  includeIncidents?: boolean;
  /** Additional provider-specific options */
  [key: string]: unknown;
}

// ─── Traffic Provider Interface ──────────────────────────────────────────────

/**
 * Interface for traffic data providers
 */
export interface ITrafficProvider {
  /** Provider name */
  readonly name: string;

  /** Whether the provider is configured and ready to use */
  readonly isConfigured: boolean;

  /**
   * Fetch traffic flow data for a geographic area
   * @param bounds - [west, south, east, north] bounding box
   * @returns Traffic flow data or null if unavailable
   */
  fetchTrafficFlow(bounds: [number, number, number, number]): Promise<ITrafficFlowData | null>;

  /**
   * Fetch a route with traffic-aware duration estimates (optional)
   * @param start - Start coordinates [lng, lat]
   * @param end - End coordinates [lng, lat]
   * @param mode - Navigation mode
   * @returns Traffic-aware route or null if not supported
   */
  fetchRouteWithTraffic?(
    start: [number, number],
    end: [number, number],
    mode: TNavigationMode
  ): Promise<ITrafficAwareRoute | null>;

  /**
   * Configure the provider with options
   * @param options - Provider configuration
   */
  configure(options: ITrafficProviderConfig): void;
}

// ─── HERE Traffic Provider ───────────────────────────────────────────────────

/**
 * Traffic provider using HERE Traffic API v7 (freemium)
 *
 * Free tier includes:
 * - 250,000 transactions/month
 * - Traffic flow and incidents
 * - No credit card required
 *
 * @see https://developer.here.com/documentation/traffic-api/dev_guide/index.html
 */
export class HereTrafficProvider implements ITrafficProvider {
  public readonly name = 'HERE Traffic API';
  private apiKey: string = '';
  private includeIncidents: boolean = true;

  public get isConfigured(): boolean {
    return this.apiKey.length > 0;
  }

  public configure(options: ITrafficProviderConfig): void {
    if (options.apiKey) {
      this.apiKey = options.apiKey;
    }
    if (typeof options.includeIncidents === 'boolean') {
      this.includeIncidents = options.includeIncidents;
    }
  }

  public async fetchTrafficFlow(
    bounds: [number, number, number, number]
  ): Promise<ITrafficFlowData | null> {
    if (!this.isConfigured) {
      console.warn('[HereTrafficProvider] No API key configured');
      return null;
    }

    const [west, south, east, north] = bounds;

    // HERE Traffic Flow API endpoint
    // Format: bbox=west,south,east,north
    const bbox = `${west},${south},${east},${north}`;
    const url = `https://data.traffic.hereapi.com/v7/flow?in=bbox:${bbox}&apiKey=${this.apiKey}`;

    try {
      const response = await fetch(url);

      if (!response.ok) {
        if (response.status === 401 || response.status === 403) {
          console.error('[HereTrafficProvider] Invalid or expired API key');
        } else if (response.status === 429) {
          console.warn('[HereTrafficProvider] Rate limit exceeded');
        } else {
          console.error(`[HereTrafficProvider] API error: ${response.status}`);
        }
        return null;
      }

      const data = await response.json();
      return this.parseHereTrafficFlow(data, bounds);
    } catch (error) {
      console.error('[HereTrafficProvider] Fetch error:', error);
      return null;
    }
  }

  /**
   * Parse HERE Traffic API response into our standard format
   */
  private parseHereTrafficFlow(
    data: IHereTrafficFlowResponse,
    bounds: [number, number, number, number]
  ): ITrafficFlowData {
    const segments: ITrafficFlowSegment[] = [];

    if (data.results) {
      for (const result of data.results) {
        if (!result.location?.shape?.links) continue;

        for (const link of result.location.shape.links) {
          // Parse the polyline points
          const coordinates = this.parseHerePolyline(link.points);
          if (coordinates.length < 2) continue;

          // Get traffic data from currentFlow
          const flow = result.currentFlow;
          const freeFlow = flow?.freeFlow || flow?.speed || 50;
          const currentSpeed = flow?.speed || freeFlow;
          const jamFactor = flow?.jamFactor || 0; // 0-10 scale

          segments.push({
            geometry: {
              type: 'LineString',
              coordinates,
            },
            congestion: Math.min(jamFactor / 10, 1), // Normalize to 0-1
            speed: currentSpeed,
            freeFlowSpeed: freeFlow,
            confidence: flow?.confidence || 0.5,
          });
        }
      }
    }

    return {
      segments,
      timestamp: new Date(),
      bounds,
    };
  }

  /**
   * Parse HERE polyline format to coordinates array
   * HERE uses lat,lng format, we need lng,lat
   */
  private parseHerePolyline(points: IHerePoint[]): [number, number][] {
    return points.map(point => [point.lng, point.lat] as [number, number]);
  }

  public async fetchRouteWithTraffic(
    start: [number, number],
    end: [number, number],
    mode: TNavigationMode
  ): Promise<ITrafficAwareRoute | null> {
    if (!this.isConfigured) {
      console.warn('[HereTrafficProvider] No API key configured');
      return null;
    }

    // Map mode to HERE transport mode
    const transportMode = mode === 'cycling' ? 'bicycle'
      : mode === 'walking' ? 'pedestrian'
      : 'car';

    // HERE Routing API v8 with traffic
    const url = new URL('https://router.hereapi.com/v8/routes');
    url.searchParams.set('apiKey', this.apiKey);
    url.searchParams.set('origin', `${start[1]},${start[0]}`); // HERE uses lat,lng
    url.searchParams.set('destination', `${end[1]},${end[0]}`);
    url.searchParams.set('transportMode', transportMode);
    url.searchParams.set('return', 'polyline,summary,turnByTurnActions');

    // Include traffic for driving mode
    if (mode === 'driving') {
      url.searchParams.set('departureTime', 'now'); // Enables live traffic
    }

    try {
      const response = await fetch(url.toString());

      if (!response.ok) {
        console.error(`[HereTrafficProvider] Routing error: ${response.status}`);
        return null;
      }

      const data = await response.json() as IHereRoutingResponse;
      return this.parseHereRoute(data);
    } catch (error) {
      console.error('[HereTrafficProvider] Routing fetch error:', error);
      return null;
    }
  }

  /**
   * Parse HERE Routing API response
   */
  private parseHereRoute(data: IHereRoutingResponse): ITrafficAwareRoute | null {
    if (!data.routes || data.routes.length === 0) {
      return null;
    }

    const route = data.routes[0];
    const section = route.sections?.[0];
    if (!section) return null;

    // Decode the polyline
    const coordinates = this.decodeFlexiblePolyline(section.polyline);

    // Calculate congestion level based on delay
    const baseDuration = section.summary?.baseDuration || section.summary?.duration || 0;
    const duration = section.summary?.duration || baseDuration;
    const delayRatio = baseDuration > 0 ? (duration - baseDuration) / baseDuration : 0;

    let congestionLevel: 'low' | 'moderate' | 'heavy' | 'severe';
    if (delayRatio < 0.1) congestionLevel = 'low';
    else if (delayRatio < 0.3) congestionLevel = 'moderate';
    else if (delayRatio < 0.5) congestionLevel = 'heavy';
    else congestionLevel = 'severe';

    // Convert HERE actions to OSRM-style legs/steps
    const legs = this.convertToOsrmLegs(section);

    return {
      geometry: {
        type: 'LineString',
        coordinates,
      },
      distance: section.summary?.length || 0,
      duration,
      durationWithoutTraffic: baseDuration,
      congestionLevel,
      legs,
    };
  }

  /**
   * Decode HERE's flexible polyline format
   * @see https://github.com/heremaps/flexible-polyline
   */
  private decodeFlexiblePolyline(encoded: string): [number, number][] {
    // Simplified decoder - for full support, use @here/flexpolyline package
    const coordinates: [number, number][] = [];

    let index = 0;
    let lat = 0;
    let lng = 0;

    // Skip header byte (version and precision info)
    const header = this.decodeUnsignedValue(encoded, index);
    index = header.nextIndex;
    const precision = Math.pow(10, header.value & 15);

    while (index < encoded.length) {
      const latResult = this.decodeSignedValue(encoded, index);
      index = latResult.nextIndex;
      lat += latResult.value;

      const lngResult = this.decodeSignedValue(encoded, index);
      index = lngResult.nextIndex;
      lng += lngResult.value;

      coordinates.push([lng / precision, lat / precision]);
    }

    return coordinates;
  }

  private decodeUnsignedValue(encoded: string, startIndex: number): { value: number; nextIndex: number } {
    let result = 0;
    let shift = 0;
    let index = startIndex;

    while (index < encoded.length) {
      const char = encoded.charCodeAt(index) - 45;
      index++;
      result |= (char & 31) << shift;
      if (char < 32) break;
      shift += 5;
    }

    return { value: result, nextIndex: index };
  }

  private decodeSignedValue(encoded: string, startIndex: number): { value: number; nextIndex: number } {
    const { value, nextIndex } = this.decodeUnsignedValue(encoded, startIndex);
    return {
      value: (value & 1) ? ~(value >> 1) : (value >> 1),
      nextIndex,
    };
  }

  /**
   * Convert HERE routing actions to OSRM-style legs
   */
  private convertToOsrmLegs(section: IHereRouteSection): IOSRMLeg[] {
    const steps = (section.turnByTurnActions || []).map(action => ({
      geometry: { type: 'LineString' as const, coordinates: [] as [number, number][] },
      maneuver: {
        type: this.mapHereActionToOsrm(action.action),
        modifier: action.direction?.toLowerCase(),
        location: [action.offset || 0, 0] as [number, number], // Simplified
      },
      name: action.currentRoad?.name?.[0]?.value || action.nextRoad?.name?.[0]?.value || '',
      distance: action.length || 0,
      duration: action.duration || 0,
      driving_side: 'right',
    }));

    return [{
      steps,
      distance: section.summary?.length || 0,
      duration: section.summary?.duration || 0,
    }];
  }

  /**
   * Map HERE action types to OSRM maneuver types
   */
  private mapHereActionToOsrm(action: string): string {
    const mapping: Record<string, string> = {
      'depart': 'depart',
      'arrive': 'arrive',
      'turn': 'turn',
      'continue': 'continue',
      'roundaboutEnter': 'roundabout',
      'roundaboutExit': 'roundabout',
      'merge': 'merge',
      'fork': 'fork',
      'uturn': 'turn',
    };
    return mapping[action] || action;
  }
}

// ─── Valhalla Traffic Provider ───────────────────────────────────────────────

/**
 * Traffic provider for self-hosted Valhalla servers
 *
 * Requires:
 * - A running Valhalla server with traffic data enabled
 * - Traffic data in Valhalla's expected format
 *
 * @see https://valhalla.github.io/valhalla/
 */
export class ValhallaTrafficProvider implements ITrafficProvider {
  public readonly name = 'Valhalla (Self-Hosted)';
  private serverUrl: string = '';
  private trafficDataUrl: string = '';

  public get isConfigured(): boolean {
    return this.serverUrl.length > 0;
  }

  public configure(options: ITrafficProviderConfig): void {
    if (options.serverUrl) {
      this.serverUrl = options.serverUrl.replace(/\/$/, ''); // Remove trailing slash
    }
    if (options.trafficDataUrl && typeof options.trafficDataUrl === 'string') {
      this.trafficDataUrl = options.trafficDataUrl;
    }
  }

  public async fetchTrafficFlow(
    bounds: [number, number, number, number]
  ): Promise<ITrafficFlowData | null> {
    if (!this.isConfigured) {
      console.warn('[ValhallaTrafficProvider] No server URL configured');
      return null;
    }

    // Valhalla doesn't have a direct traffic flow API like HERE
    // This would require custom traffic data endpoint setup
    if (!this.trafficDataUrl) {
      console.info('[ValhallaTrafficProvider] Traffic flow visualization requires trafficDataUrl');
      return null;
    }

    try {
      const [west, south, east, north] = bounds;
      const url = `${this.trafficDataUrl}?bbox=${west},${south},${east},${north}`;
      const response = await fetch(url);

      if (!response.ok) {
        console.error(`[ValhallaTrafficProvider] Traffic data error: ${response.status}`);
        return null;
      }

      const data = await response.json();
      return this.parseValhallaTrafficData(data, bounds);
    } catch (error) {
      console.error('[ValhallaTrafficProvider] Fetch error:', error);
      return null;
    }
  }

  /**
   * Parse custom traffic data format (user-defined schema)
   */
  private parseValhallaTrafficData(
    data: IValhallaTrafficData,
    bounds: [number, number, number, number]
  ): ITrafficFlowData {
    const segments: ITrafficFlowSegment[] = [];

    if (data.segments) {
      for (const seg of data.segments) {
        segments.push({
          geometry: seg.geometry,
          congestion: seg.congestion || 0,
          speed: seg.speed || 0,
          freeFlowSpeed: seg.freeFlowSpeed || seg.speed || 50,
          confidence: seg.confidence || 0.5,
        });
      }
    }

    return {
      segments,
      timestamp: new Date(),
      bounds,
    };
  }

  public async fetchRouteWithTraffic(
    start: [number, number],
    end: [number, number],
    mode: TNavigationMode
  ): Promise<ITrafficAwareRoute | null> {
    if (!this.isConfigured) {
      console.warn('[ValhallaTrafficProvider] No server URL configured');
      return null;
    }

    // Map mode to Valhalla costing model
    const costing = mode === 'cycling' ? 'bicycle'
      : mode === 'walking' ? 'pedestrian'
      : 'auto';

    const requestBody = {
      locations: [
        { lat: start[1], lon: start[0] },
        { lat: end[1], lon: end[0] },
      ],
      costing,
      costing_options: {
        [costing]: {
          use_traffic: true, // Enable traffic consideration
        },
      },
      directions_options: {
        units: 'kilometers',
      },
    };

    try {
      const response = await fetch(`${this.serverUrl}/route`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(requestBody),
      });

      if (!response.ok) {
        console.error(`[ValhallaTrafficProvider] Routing error: ${response.status}`);
        return null;
      }

      const data = await response.json() as IValhallaRouteResponse;
      return this.parseValhallaRoute(data);
    } catch (error) {
      console.error('[ValhallaTrafficProvider] Routing fetch error:', error);
      return null;
    }
  }

  /**
   * Parse Valhalla routing response
   */
  private parseValhallaRoute(data: IValhallaRouteResponse): ITrafficAwareRoute | null {
    if (!data.trip?.legs?.[0]) {
      return null;
    }

    const leg = data.trip.legs[0];
    const summary = data.trip.summary;

    // Decode Valhalla's polyline6 format
    const coordinates = this.decodePolyline6(leg.shape);

    // Calculate congestion level
    const delayRatio = summary.time > 0
      ? (summary.time - (summary.time * 0.9)) / summary.time // Estimate without traffic
      : 0;

    let congestionLevel: 'low' | 'moderate' | 'heavy' | 'severe';
    if (delayRatio < 0.1) congestionLevel = 'low';
    else if (delayRatio < 0.3) congestionLevel = 'moderate';
    else if (delayRatio < 0.5) congestionLevel = 'heavy';
    else congestionLevel = 'severe';

    // Convert Valhalla maneuvers to OSRM format
    const steps = (leg.maneuvers || []).map(maneuver => ({
      geometry: { type: 'LineString' as const, coordinates: [] as [number, number][] },
      maneuver: {
        type: this.mapValhallaManeuverToOsrm(maneuver.type),
        modifier: maneuver.modifier,
        location: [maneuver.begin_shape_index, 0] as [number, number],
      },
      name: maneuver.street_names?.[0] || '',
      distance: maneuver.length * 1000, // Convert km to meters
      duration: maneuver.time,
      driving_side: 'right',
    }));

    return {
      geometry: {
        type: 'LineString',
        coordinates,
      },
      distance: summary.length * 1000, // Convert km to meters
      duration: summary.time,
      durationWithoutTraffic: summary.time * 0.9, // Estimate
      congestionLevel,
      legs: [{
        steps,
        distance: summary.length * 1000,
        duration: summary.time,
      }],
    };
  }

  /**
   * Decode polyline6 format (Valhalla uses 6 decimal precision)
   */
  private decodePolyline6(encoded: string): [number, number][] {
    const coordinates: [number, number][] = [];
    let index = 0;
    let lat = 0;
    let lng = 0;

    while (index < encoded.length) {
      // Decode latitude
      let shift = 0;
      let result = 0;
      let byte: number;

      do {
        byte = encoded.charCodeAt(index++) - 63;
        result |= (byte & 0x1f) << shift;
        shift += 5;
      } while (byte >= 0x20);

      lat += (result & 1) ? ~(result >> 1) : (result >> 1);

      // Decode longitude
      shift = 0;
      result = 0;

      do {
        byte = encoded.charCodeAt(index++) - 63;
        result |= (byte & 0x1f) << shift;
        shift += 5;
      } while (byte >= 0x20);

      lng += (result & 1) ? ~(result >> 1) : (result >> 1);

      coordinates.push([lng / 1e6, lat / 1e6]);
    }

    return coordinates;
  }

  /**
   * Map Valhalla maneuver types to OSRM types
   */
  private mapValhallaManeuverToOsrm(type: number): string {
    const mapping: Record<number, string> = {
      0: 'none',
      1: 'depart',
      2: 'depart',
      3: 'arrive',
      4: 'arrive',
      5: 'arrive',
      6: 'continue',
      7: 'continue',
      8: 'turn',
      9: 'turn',
      10: 'turn',
      11: 'turn',
      12: 'turn',
      13: 'turn',
      14: 'turn',
      15: 'turn',
      16: 'turn',
      17: 'roundabout',
      18: 'roundabout',
      19: 'roundabout',
      20: 'roundabout',
      21: 'fork',
      22: 'fork',
      23: 'merge',
      24: 'merge',
      25: 'merge',
      26: 'notification',
      27: 'notification',
    };
    return mapping[type] || 'continue';
  }
}

// ─── HERE API Response Types ─────────────────────────────────────────────────

interface IHerePoint {
  lat: number;
  lng: number;
}

interface IHereTrafficFlowResponse {
  results?: Array<{
    location?: {
      shape?: {
        links?: Array<{
          points: IHerePoint[];
        }>;
      };
    };
    currentFlow?: {
      speed?: number;
      freeFlow?: number;
      jamFactor?: number;
      confidence?: number;
    };
  }>;
}

interface IHereRoutingResponse {
  routes?: Array<{
    sections?: IHereRouteSection[];
  }>;
}

interface IHereRouteSection {
  polyline: string;
  summary?: {
    duration: number;
    baseDuration?: number;
    length: number;
  };
  turnByTurnActions?: Array<{
    action: string;
    direction?: string;
    offset?: number;
    length?: number;
    duration?: number;
    currentRoad?: { name?: Array<{ value: string }> };
    nextRoad?: { name?: Array<{ value: string }> };
  }>;
}

// ─── Valhalla API Response Types ─────────────────────────────────────────────

interface IValhallaTrafficData {
  segments?: Array<{
    geometry: GeoJSON.LineString;
    congestion?: number;
    speed?: number;
    freeFlowSpeed?: number;
    confidence?: number;
  }>;
}

interface IValhallaRouteResponse {
  trip?: {
    summary: {
      time: number;
      length: number;
    };
    legs?: Array<{
      shape: string;
      maneuvers?: Array<{
        type: number;
        modifier?: string;
        begin_shape_index: number;
        street_names?: string[];
        length: number;
        time: number;
      }>;
    }>;
  };
}
