import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '../prisma/prisma.service';
import { firstValueFrom } from 'rxjs';
import { Flight } from '@prisma/client';

// Tracking phases - determines polling priority
const PHASE = {
  FAR_OUT: 'FAR_OUT',               // >24h before departure - no auto-poll
  PRE_DEPARTURE: 'PRE_DEPARTURE',   // 6-24h before departure
  DEPARTURE_WINDOW: 'DEPARTURE_WINDOW', // 0-6h before departure
  ACTIVE: 'ACTIVE',                 // In flight
  ARRIVAL_WINDOW: 'ARRIVAL_WINDOW', // Within 1h of ETA
  LANDED: 'LANDED',                 // Flight has landed
  TERMINAL: 'TERMINAL',             // Cancelled/diverted/incident - terminal state
} as const;

// Priority scores for each phase (higher = more urgent)
const PHASE_PRIORITY: Record<string, number> = {
  [PHASE.ARRIVAL_WINDOW]: 100,
  [PHASE.ACTIVE]: 60,
  [PHASE.DEPARTURE_WINDOW]: 40,
  [PHASE.PRE_DEPARTURE]: 10,
  [PHASE.FAR_OUT]: 0,
  [PHASE.LANDED]: 0,
  [PHASE.TERMINAL]: 0,
};

// Minimum minutes between polls per phase (to prevent wasting budget)
const MIN_POLL_INTERVAL: Record<string, number> = {
  [PHASE.ARRIVAL_WINDOW]: 20,
  [PHASE.ACTIVE]: 45,
  [PHASE.DEPARTURE_WINDOW]: 60,
  [PHASE.PRE_DEPARTURE]: 180,
  [PHASE.FAR_OUT]: Infinity,
  [PHASE.LANDED]: Infinity,
  [PHASE.TERMINAL]: Infinity,
};

// Map AviationStack status to our tracking phase
const STATUS_TO_TERMINAL: string[] = ['cancelled', 'incident', 'diverted'];

@Injectable()
export class FlightTrackingService {
  private readonly logger = new Logger(FlightTrackingService.name);
  private readonly apiKey: string;
  private readonly baseUrl = 'http://api.aviationstack.com/v1';

  constructor(
    private prisma: PrismaService,
    private httpService: HttpService,
    private configService: ConfigService,
  ) {
    this.apiKey = this.configService.get('AVIATIONSTACK_API_KEY') || '';
    if (this.apiKey) {
      this.logger.log('AviationStack API key configured - flight tracking enabled');
    } else {
      this.logger.warn('AviationStack API key not configured - flight tracking disabled');
    }
  }

  // ============================================
  // Cron Job: Smart Flight Polling (every 5 min)
  // ============================================

  @Cron('*/5 * * * *')
  async pollFlightsCron(): Promise<void> {
    if (!this.apiKey) return;

    try {
      // 1. Check budget
      const budget = await this.getOrCreateBudget();
      const budgetPercent = (budget.requestsUsed / budget.requestLimit) * 100;

      if (budgetPercent >= 95) {
        this.logger.debug('Flight API budget exhausted (>=95%) - skipping auto-poll');
        return;
      }

      // 2. Get all trackable flights (not in terminal states)
      const flights = await this.prisma.flight.findMany({
        where: {
          autoTrackEnabled: true,
          trackingPhase: {
            notIn: [PHASE.LANDED, PHASE.TERMINAL, PHASE.FAR_OUT],
          },
        },
        include: { vip: true },
      });

      if (flights.length === 0) return;

      // 3. Recalculate phases and score each flight
      const candidates: { flight: Flight; phase: string; priority: number }[] = [];

      for (const flight of flights) {
        const phase = this.calculateTrackingPhase(flight);

        // Update phase in DB if changed
        if (phase !== flight.trackingPhase) {
          await this.prisma.flight.update({
            where: { id: flight.id },
            data: { trackingPhase: phase },
          });
        }

        // Skip phases that shouldn't be polled
        if (PHASE_PRIORITY[phase] === 0) continue;

        // Budget conservation: if >80% used, only poll high-priority
        if (budgetPercent > 80 && PHASE_PRIORITY[phase] < 60) continue;

        // Check minimum polling interval
        if (!this.shouldPoll(flight, phase)) continue;

        candidates.push({
          flight,
          phase,
          priority: PHASE_PRIORITY[phase],
        });
      }

      if (candidates.length === 0) return;

      // 4. Pick the highest-priority candidate
      candidates.sort((a, b) => b.priority - a.priority);
      const best = candidates[0];

      this.logger.log(
        `Auto-polling flight ${best.flight.flightNumber} (phase: ${best.phase}, priority: ${best.priority}, budget: ${budget.requestsUsed}/${budget.requestLimit})`,
      );

      // 5. Poll it
      await this.callAviationStackAndUpdate(best.flight);
    } catch (error) {
      this.logger.error(`Flight polling cron error: ${error.message}`, error.stack);
    }
  }

  // ============================================
  // Manual Refresh (coordinator-triggered)
  // ============================================

  async refreshFlight(flightId: string) {
    const flight = await this.prisma.flight.findUnique({
      where: { id: flightId },
      include: { vip: true },
    });

    if (!flight) {
      throw new NotFoundException(`Flight ${flightId} not found`);
    }

    if (!this.apiKey) {
      return {
        message: 'Flight tracking API not configured',
        flight,
      };
    }

    const updated = await this.callAviationStackAndUpdate(flight);
    return updated;
  }

  async refreshActiveFlights() {
    if (!this.apiKey) {
      return { refreshed: 0, skipped: 0, budgetRemaining: 0, message: 'API key not configured' };
    }

    const budget = await this.getOrCreateBudget();
    const remaining = budget.requestLimit - budget.requestsUsed;

    // Get active flights that would benefit from refresh
    const flights = await this.prisma.flight.findMany({
      where: {
        trackingPhase: {
          in: [PHASE.ACTIVE, PHASE.ARRIVAL_WINDOW, PHASE.DEPARTURE_WINDOW],
        },
      },
      include: { vip: true },
      orderBy: { scheduledDeparture: 'asc' },
    });

    let refreshed = 0;
    let skipped = 0;

    for (const flight of flights) {
      if (refreshed >= remaining) {
        skipped += flights.length - refreshed - skipped;
        break;
      }

      try {
        await this.callAviationStackAndUpdate(flight);
        refreshed++;
      } catch (error) {
        this.logger.error(`Failed to refresh flight ${flight.flightNumber}: ${error.message}`);
        skipped++;
      }
    }

    const updatedBudget = await this.getOrCreateBudget();
    return {
      refreshed,
      skipped,
      budgetRemaining: updatedBudget.requestLimit - updatedBudget.requestsUsed,
    };
  }

  // ============================================
  // Budget Management
  // ============================================

  async getBudgetStatus() {
    const budget = await this.getOrCreateBudget();
    return {
      used: budget.requestsUsed,
      limit: budget.requestLimit,
      remaining: budget.requestLimit - budget.requestsUsed,
      month: budget.monthYear,
    };
  }

  private async getOrCreateBudget() {
    const monthYear = this.getCurrentMonthYear();

    let budget = await this.prisma.flightApiBudget.findUnique({
      where: { monthYear },
    });

    if (!budget) {
      budget = await this.prisma.flightApiBudget.create({
        data: { monthYear, requestLimit: 100 },
      });
    }

    return budget;
  }

  private async incrementBudget() {
    const monthYear = this.getCurrentMonthYear();
    return this.prisma.flightApiBudget.upsert({
      where: { monthYear },
      update: {
        requestsUsed: { increment: 1 },
        lastRequestAt: new Date(),
      },
      create: {
        monthYear,
        requestsUsed: 1,
        requestLimit: 100,
        lastRequestAt: new Date(),
      },
    });
  }

  private getCurrentMonthYear(): string {
    const now = new Date();
    return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
  }

  // ============================================
  // Phase Calculation
  // ============================================

  calculateTrackingPhase(flight: Flight): string {
    const now = new Date();
    const status = flight.status?.toLowerCase();

    // Terminal states
    if (status === 'landed' || flight.actualArrival) return PHASE.LANDED;
    if (STATUS_TO_TERMINAL.includes(status || '')) return PHASE.TERMINAL;

    // Active in flight
    if (status === 'active') {
      // Check if within arrival window
      const eta = flight.estimatedArrival || flight.scheduledArrival;
      if (eta) {
        const minutesToArrival = (new Date(eta).getTime() - now.getTime()) / 60000;
        if (minutesToArrival <= 60) return PHASE.ARRIVAL_WINDOW;
      }
      return PHASE.ACTIVE;
    }

    // Pre-departure phases based on scheduled departure
    const departure = flight.estimatedDeparture || flight.scheduledDeparture;
    if (!departure) return PHASE.FAR_OUT;

    const hoursUntilDeparture = (new Date(departure).getTime() - now.getTime()) / 3600000;

    if (hoursUntilDeparture <= 0) {
      // Past scheduled departure but no "active" status from API
      // Could be delayed at gate - treat as departure window
      return PHASE.DEPARTURE_WINDOW;
    }
    if (hoursUntilDeparture <= 6) return PHASE.DEPARTURE_WINDOW;
    if (hoursUntilDeparture <= 24) return PHASE.PRE_DEPARTURE;
    return PHASE.FAR_OUT;
  }

  // ============================================
  // Polling Decision
  // ============================================

  private shouldPoll(flight: Flight, phase: string): boolean {
    const minInterval = MIN_POLL_INTERVAL[phase];
    if (!isFinite(minInterval)) return false;
    if (!flight.lastPolledAt) return true; // Never polled

    const minutesSincePoll = (Date.now() - new Date(flight.lastPolledAt).getTime()) / 60000;
    return minutesSincePoll >= minInterval;
  }

  // ============================================
  // AviationStack API Integration
  // ============================================

  private async callAviationStackAndUpdate(flight: Flight & { vip?: any }): Promise<Flight> {
    const flightDate = flight.flightDate
      ? new Date(flight.flightDate).toISOString().split('T')[0]
      : undefined;

    try {
      const params: any = {
        access_key: this.apiKey,
        flight_iata: flight.flightNumber,
      };

      if (flightDate) {
        params.flight_date = flightDate;
      }

      const response = await firstValueFrom(
        this.httpService.get(`${this.baseUrl}/flights`, {
          params,
          timeout: 15000,
        }),
      );

      // Increment budget after successful call
      await this.incrementBudget();

      const data = response.data as any;

      if (data?.error) {
        this.logger.warn(`AviationStack API error for ${flight.flightNumber}: ${data.error.message || JSON.stringify(data.error)}`);
        // Still update lastPolledAt so we don't spam on errors
        return this.prisma.flight.update({
          where: { id: flight.id },
          data: { lastPolledAt: new Date(), pollCount: { increment: 1 } },
          include: { vip: true },
        });
      }

      if (data?.data && data.data.length > 0) {
        const apiResult = data.data[0];
        const updateData = this.parseAviationStackResponse(apiResult);

        // Calculate new phase based on updated data
        const tempFlight = { ...flight, ...updateData };
        const newPhase = this.calculateTrackingPhase(tempFlight as Flight);

        const updated = await this.prisma.flight.update({
          where: { id: flight.id },
          data: {
            ...updateData,
            trackingPhase: newPhase,
            lastPolledAt: new Date(),
            pollCount: { increment: 1 },
            lastApiResponse: apiResult,
          },
          include: { vip: true },
        });

        this.logger.log(
          `Updated flight ${flight.flightNumber}: status=${updated.status}, phase=${newPhase}, delay=${updated.arrivalDelay || 0}min`,
        );

        return updated;
      }

      // Flight not found in API
      this.logger.warn(`Flight ${flight.flightNumber} not found in AviationStack API`);
      return this.prisma.flight.update({
        where: { id: flight.id },
        data: { lastPolledAt: new Date(), pollCount: { increment: 1 } },
        include: { vip: true },
      });
    } catch (error) {
      this.logger.error(`AviationStack API call failed for ${flight.flightNumber}: ${error.message}`);
      // Still update lastPolledAt on error to prevent rapid retries
      return this.prisma.flight.update({
        where: { id: flight.id },
        data: { lastPolledAt: new Date() },
        include: { vip: true },
      });
    }
  }

  // ============================================
  // Response Parser
  // ============================================

  private parseAviationStackResponse(apiData: any): Partial<Flight> {
    const update: any = {};

    // Flight status
    if (apiData.flight_status) {
      update.status = apiData.flight_status;
    }

    // Departure info
    if (apiData.departure) {
      const dep = apiData.departure;
      if (dep.terminal) update.departureTerminal = dep.terminal;
      if (dep.gate) update.departureGate = dep.gate;
      if (dep.delay != null) update.departureDelay = dep.delay;
      if (dep.scheduled) update.scheduledDeparture = new Date(dep.scheduled);
      if (dep.estimated) update.estimatedDeparture = new Date(dep.estimated);
      if (dep.actual) update.actualDeparture = new Date(dep.actual);
      // Store departure airport name if we only had IATA code
      if (dep.iata && !update.departureAirport) update.departureAirport = dep.iata;
    }

    // Arrival info
    if (apiData.arrival) {
      const arr = apiData.arrival;
      if (arr.terminal) update.arrivalTerminal = arr.terminal;
      if (arr.gate) update.arrivalGate = arr.gate;
      if (arr.baggage) update.arrivalBaggage = arr.baggage;
      if (arr.delay != null) update.arrivalDelay = arr.delay;
      if (arr.scheduled) update.scheduledArrival = new Date(arr.scheduled);
      if (arr.estimated) update.estimatedArrival = new Date(arr.estimated);
      if (arr.actual) update.actualArrival = new Date(arr.actual);
      if (arr.iata && !update.arrivalAirport) update.arrivalAirport = arr.iata;
    }

    // Airline info
    if (apiData.airline) {
      if (apiData.airline.name) update.airlineName = apiData.airline.name;
      if (apiData.airline.iata) update.airlineIata = apiData.airline.iata;
    }

    // Aircraft info
    if (apiData.aircraft?.iata) {
      update.aircraftType = apiData.aircraft.iata;
    }

    // Live tracking data (may not be available on free tier)
    if (apiData.live) {
      const live = apiData.live;
      if (live.latitude != null) update.liveLatitude = live.latitude;
      if (live.longitude != null) update.liveLongitude = live.longitude;
      if (live.altitude != null) update.liveAltitude = live.altitude;
      if (live.speed_horizontal != null) update.liveSpeed = live.speed_horizontal;
      if (live.direction != null) update.liveDirection = live.direction;
      if (live.is_ground != null) update.liveIsGround = live.is_ground;
      if (live.updated) update.liveUpdatedAt = new Date(live.updated);
    }

    return update;
  }
}
