import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  haversineDistanceMeters,
  filterTrajectoryOutliers,
  TimedGpsPoint,
  RouteSegmentFeature,
  SegmentType,
  LatLng,
} from '../../common/utils/geo.util';

export interface TrajectoryProcessingResult {
  totalDistanceMeters: number;
  gpsVerifiedMeters: number;
  mapMatchedMeters: number;
  reconstructedMeters: number;
  overallConfidenceScore: number;
  routeGeometry: {
    type: 'FeatureCollection';
    features: RouteSegmentFeature[];
  };
}

@Injectable()
export class TrajectoryReconstructionService {

  constructor(private readonly configService: ConfigService) {}

  /**
   * Main entry point to process a trip trajectory.
   * Performs outlier filtering, gap reconstruction, segment confidence scoring,
   * and geodesic distance calculation over valid route polylines.
   */
  public processTrajectory(rawPoints: TimedGpsPoint[]): TrajectoryProcessingResult {
    if (!rawPoints || rawPoints.length === 0) {
      return this.emptyResult();
    }

    // 1. Sort by timestamp ascending
    const sortedPoints = [...rawPoints].sort(
      (a, b) => a.timestamp.getTime() - b.timestamp.getTime(),
    );

    // 2. Outlier Removal & Noise Cleaning Filter
    const cleanPoints = filterTrajectoryOutliers(sortedPoints);

    if (cleanPoints.length < 2) {
      return this.emptyResult();
    }

    // 3. Segment Analysis & Signal Gap Reconstruction
    const features: RouteSegmentFeature[] = [];
    let gpsVerifiedM = 0;
    let mapMatchedM = 0;
    let reconstructedM = 0;
    let totalConfidenceSum = 0;
    let totalSegments = 0;

    let currentSegmentCoords: Array<[number, number]> = [];
    let currentSegmentType: SegmentType = 'GPS_VERIFIED';
    let currentAccuracies: number[] = [];
    let currentSpeeds: number[] = [];

    const GAP_TIME_THRESHOLD_SEC = 45;
    const GAP_DIST_THRESHOLD_M = 300;

    for (let i = 0; i < cleanPoints.length; i++) {
      const point = cleanPoints[i];
      const coord: [number, number] = [point.longitude, point.latitude];

      if (i === 0) {
        currentSegmentCoords.push(coord);
        if (point.accuracy != null) currentAccuracies.push(point.accuracy);
        if (point.speed != null) currentSpeeds.push(point.speed);
        continue;
      }

      const prev = cleanPoints[i - 1];
      const dtSec = (point.timestamp.getTime() - prev.timestamp.getTime()) / 1000;
      const distM = haversineDistanceMeters(prev, point);

      // Check if a signal gap / tunnel loss occurred
      const isGap = dtSec > GAP_TIME_THRESHOLD_SEC || distM > GAP_DIST_THRESHOLD_M;

      if (isGap) {
        // Flush previous active segment
        if (currentSegmentCoords.length >= 2) {
          const segFeature = this.createSegmentFeature(
            currentSegmentCoords,
            currentSegmentType,
            currentAccuracies,
            currentSpeeds,
          );
          features.push(segFeature);

          if (currentSegmentType === 'GPS_VERIFIED') {
            gpsVerifiedM += segFeature.properties.distanceMeters;
          } else {
            mapMatchedM += segFeature.properties.distanceMeters;
          }
          totalConfidenceSum += segFeature.properties.confidence;
          totalSegments++;
        }

        // Generate ESTIMATED_RECONSTRUCTED segment between gap bounds (prev -> point)
        const gapFeature = this.reconstructGapSegment(prev, point, dtSec, distM);
        features.push(gapFeature);
        reconstructedM += gapFeature.properties.distanceMeters;
        totalConfidenceSum += gapFeature.properties.confidence;
        totalSegments++;

        // Reset next segment starting from current point
        currentSegmentCoords = [coord];
        currentAccuracies = point.accuracy != null ? [point.accuracy] : [];
        currentSpeeds = point.speed != null ? [point.speed] : [];
        currentSegmentType = (point.accuracy ?? 10) < 15 ? 'GPS_VERIFIED' : 'MAP_MATCHED';
      } else {
        currentSegmentCoords.push(coord);
        if (point.accuracy != null) currentAccuracies.push(point.accuracy);
        if (point.speed != null) currentSpeeds.push(point.speed);

        // Transition logic between GPS_VERIFIED vs MAP_MATCHED based on accuracy
        const avgAcc = currentAccuracies.reduce((a, b) => a + b, 0) / Math.max(1, currentAccuracies.length);
        if (avgAcc > 15 && currentSegmentType === 'GPS_VERIFIED') {
          currentSegmentType = 'MAP_MATCHED';
        }
      }
    }

    // Flush final segment
    if (currentSegmentCoords.length >= 2) {
      const segFeature = this.createSegmentFeature(
        currentSegmentCoords,
        currentSegmentType,
        currentAccuracies,
        currentSpeeds,
      );
      features.push(segFeature);

      if (currentSegmentType === 'GPS_VERIFIED') {
        gpsVerifiedM += segFeature.properties.distanceMeters;
      } else {
        mapMatchedM += segFeature.properties.distanceMeters;
      }
      totalConfidenceSum += segFeature.properties.confidence;
      totalSegments++;
    }

    const totalDistanceMeters = gpsVerifiedM + mapMatchedM + reconstructedM;
    const overallConfidenceScore =
      totalSegments > 0 ? Math.min(1.0, totalConfidenceSum / totalSegments) : 1.0;

    return {
      totalDistanceMeters: Math.round(totalDistanceMeters * 10) / 10,
      gpsVerifiedMeters: Math.round(gpsVerifiedM * 10) / 10,
      mapMatchedMeters: Math.round(mapMatchedM * 10) / 10,
      reconstructedMeters: Math.round(reconstructedM * 10) / 10,
      overallConfidenceScore: Math.round(overallConfidenceScore * 1000) / 1000,
      routeGeometry: {
        type: 'FeatureCollection',
        features,
      },
    };
  }

  private createSegmentFeature(
    coords: Array<[number, number]>,
    type: SegmentType,
    accuracies: number[],
    speeds: number[],
  ): RouteSegmentFeature {
    let segmentDistM = 0;
    for (let i = 1; i < coords.length; i++) {
      const p1: LatLng = { latitude: coords[i - 1][1], longitude: coords[i - 1][0] };
      const p2: LatLng = { latitude: coords[i][1], longitude: coords[i][0] };
      segmentDistM += haversineDistanceMeters(p1, p2);
    }

    const avgAcc = accuracies.length
      ? accuracies.reduce((a, b) => a + b, 0) / accuracies.length
      : 10;

    const avgSpeedMps = speeds.length
      ? speeds.reduce((a, b) => a + b, 0) / speeds.length
      : 0;

    // Confidence formula based on reported accuracy and match type
    const accConfidence = Math.max(0, 1 - avgAcc / 30);
    const typeConfidence = type === 'GPS_VERIFIED' ? 0.95 : 0.80;
    const confidence = Math.round((accConfidence * 0.4 + typeConfidence * 0.6) * 100) / 100;

    return {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: coords,
      },
      properties: {
        segmentType: type,
        confidence,
        avgSpeedKmh: Math.round(avgSpeedMps * 3.6 * 10) / 10,
        distanceMeters: Math.round(segmentDistM * 10) / 10,
      },
    };
  }

  private reconstructGapSegment(
    start: TimedGpsPoint,
    end: TimedGpsPoint,
    dtSec: number,
    distM: number,
  ): RouteSegmentFeature {
    // Generate linear path as fallback estimation for the signal gap
    const coords: Array<[number, number]> = [
      [start.longitude, start.latitude],
      [end.longitude, end.latitude],
    ];

    const estimatedSpeedMps = distM / Math.max(1, dtSec);
    const confidence = Math.max(0.40, Math.min(0.75, 1.0 - distM / 5000));

    return {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: coords,
      },
      properties: {
        segmentType: 'ESTIMATED_RECONSTRUCTED',
        confidence: Math.round(confidence * 100) / 100,
        avgSpeedKmh: Math.round(estimatedSpeedMps * 3.6 * 10) / 10,
        distanceMeters: Math.round(distM * 10) / 10,
        reason: `GPS_SIGNAL_GAP_${Math.round(dtSec)}S`,
      },
    };
  }

  private emptyResult(): TrajectoryProcessingResult {
    return {
      totalDistanceMeters: 0,
      gpsVerifiedMeters: 0,
      mapMatchedMeters: 0,
      reconstructedMeters: 0,
      overallConfidenceScore: 1.0,
      routeGeometry: {
        type: 'FeatureCollection',
        features: [],
      },
    };
  }
}
