/**
 * Analytics Service
 *
 * Calculates KPIs and analytics data for dashboard
 */

import { Injectable, Inject } from '@nestjs/common';
import { BOOKING_REPOSITORY } from '@domain/ports/out/booking.repository';
import { BookingRepository } from '@domain/ports/out/booking.repository';
import { RATE_QUOTE_REPOSITORY } from '@domain/ports/out/rate-quote.repository';
import { RateQuoteRepository } from '@domain/ports/out/rate-quote.repository';
import { TypeOrmCsvBookingRepository } from '../../infrastructure/persistence/typeorm/repositories/csv-booking.repository';
import { CsvBooking, CsvBookingStatus } from '@domain/entities/csv-booking.entity';

export interface DashboardKPIs {
  bookingsThisMonth: number;
  totalTEUs: number;
  estimatedRevenue: number;
  pendingConfirmations: number;
  bookingsThisMonthChange: number; // % change from last month
  totalTEUsChange: number;
  estimatedRevenueChange: number;
  pendingConfirmationsChange: number;
}

export interface BookingsChartData {
  labels: string[]; // Month names
  data: number[]; // Booking counts
}

export interface TopTradeLane {
  route: string;
  originPort: string;
  destinationPort: string;
  bookingCount: number;
  totalTEUs: number;
  avgPrice: number;
}

export interface DashboardAlert {
  id: string;
  type: 'delay' | 'confirmation' | 'document' | 'payment' | 'info';
  severity: 'low' | 'medium' | 'high' | 'critical';
  title: string;
  message: string;
  bookingId?: string;
  bookingNumber?: string;
  createdAt: Date;
  isRead: boolean;
}

export interface CsvBookingKPIs {
  totalAccepted: number;
  totalRejected: number;
  totalPending: number;
  totalWeightAcceptedKG: number;
  totalVolumeAcceptedCBM: number;
  acceptanceRate: number; // percentage
  acceptedThisMonth: number;
  rejectedThisMonth: number;
}

export interface TopCarrier {
  carrierName: string;
  totalBookings: number;
  acceptedBookings: number;
  rejectedBookings: number;
  acceptanceRate: number;
  totalWeightKG: number;
  totalVolumeCBM: number;
  avgPriceUSD: number;
}

@Injectable()
export class AnalyticsService {
  constructor(
    @Inject(BOOKING_REPOSITORY)
    private readonly bookingRepository: BookingRepository,
    @Inject(RATE_QUOTE_REPOSITORY)
    private readonly rateQuoteRepository: RateQuoteRepository,
    private readonly csvBookingRepository: TypeOrmCsvBookingRepository
  ) {}

  /**
   * Calculate dashboard KPIs
   * Cached for 1 hour
   */
  async calculateKPIs(organizationId: string): Promise<DashboardKPIs> {
    const now = new Date();
    const thisMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
    const lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
    const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59);

    // Get all bookings for organization
    const allBookings = await this.bookingRepository.findByOrganization(organizationId);

    // This month bookings
    const thisMonthBookings = allBookings.filter(b => b.createdAt >= thisMonthStart);

    // Last month bookings
    const lastMonthBookings = allBookings.filter(
      b => b.createdAt >= lastMonthStart && b.createdAt <= lastMonthEnd
    );

    // Calculate total TEUs (20' = 1 TEU, 40' = 2 TEU)
    // Each container is an individual entity, so we count them
    const calculateTEUs = (bookings: typeof allBookings): number => {
      return bookings.reduce((total, booking) => {
        return (
          total +
          booking.containers.reduce((containerTotal, container) => {
            const teu = container.type.startsWith('20') ? 1 : 2;
            return containerTotal + teu; // Each container counts as 1 or 2 TEU
          }, 0)
        );
      }, 0);
    };

    const totalTEUsThisMonth = calculateTEUs(thisMonthBookings);
    const totalTEUsLastMonth = calculateTEUs(lastMonthBookings);

    // Calculate estimated revenue (from rate quotes)
    const calculateRevenue = async (bookings: typeof allBookings): Promise<number> => {
      let total = 0;
      for (const booking of bookings) {
        try {
          const rateQuote = await this.rateQuoteRepository.findById(booking.rateQuoteId);
          if (rateQuote) {
            total += rateQuote.pricing.totalAmount;
          }
        } catch (error) {
          // Skip if rate quote not found
          continue;
        }
      }
      return total;
    };

    const estimatedRevenueThisMonth = await calculateRevenue(thisMonthBookings);
    const estimatedRevenueLastMonth = await calculateRevenue(lastMonthBookings);

    // Pending confirmations (status = pending_confirmation)
    const pendingThisMonth = thisMonthBookings.filter(
      b => b.status.value === 'pending_confirmation'
    ).length;
    const pendingLastMonth = lastMonthBookings.filter(
      b => b.status.value === 'pending_confirmation'
    ).length;

    // Calculate percentage changes
    const calculateChange = (current: number, previous: number): number => {
      if (previous === 0) return current > 0 ? 100 : 0;
      return ((current - previous) / previous) * 100;
    };

    return {
      bookingsThisMonth: thisMonthBookings.length,
      totalTEUs: totalTEUsThisMonth,
      estimatedRevenue: estimatedRevenueThisMonth,
      pendingConfirmations: pendingThisMonth,
      bookingsThisMonthChange: calculateChange(thisMonthBookings.length, lastMonthBookings.length),
      totalTEUsChange: calculateChange(totalTEUsThisMonth, totalTEUsLastMonth),
      estimatedRevenueChange: calculateChange(estimatedRevenueThisMonth, estimatedRevenueLastMonth),
      pendingConfirmationsChange: calculateChange(pendingThisMonth, pendingLastMonth),
    };
  }

  /**
   * Get bookings chart data for last 6 months
   */
  async getBookingsChartData(organizationId: string): Promise<BookingsChartData> {
    const now = new Date();
    const labels: string[] = [];
    const data: number[] = [];

    // Get bookings for last 6 months
    const allBookings = await this.bookingRepository.findByOrganization(organizationId);

    for (let i = 5; i >= 0; i--) {
      const monthDate = new Date(now.getFullYear(), now.getMonth() - i, 1);
      const monthEnd = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59);

      // Month label (e.g., "Jan 2025")
      const monthLabel = monthDate.toLocaleDateString('en-US', {
        month: 'short',
        year: 'numeric',
      });
      labels.push(monthLabel);

      // Count bookings in this month
      const count = allBookings.filter(
        b => b.createdAt >= monthDate && b.createdAt <= monthEnd
      ).length;
      data.push(count);
    }

    return { labels, data };
  }

  /**
   * Get top 5 trade lanes
   */
  async getTopTradeLanes(organizationId: string): Promise<TopTradeLane[]> {
    const allBookings = await this.bookingRepository.findByOrganization(organizationId);

    // Group by route (origin-destination)
    const routeMap = new Map<
      string,
      {
        originPort: string;
        destinationPort: string;
        bookingCount: number;
        totalTEUs: number;
        totalPrice: number;
      }
    >();

    for (const booking of allBookings) {
      try {
        const rateQuote = await this.rateQuoteRepository.findById(booking.rateQuoteId);
        if (!rateQuote) continue;

        // Get first and last ports from route
        const originPort = rateQuote.route[0]?.portCode || 'UNKNOWN';
        const destinationPort = rateQuote.route[rateQuote.route.length - 1]?.portCode || 'UNKNOWN';
        const routeKey = `${originPort}-${destinationPort}`;

        if (!routeMap.has(routeKey)) {
          routeMap.set(routeKey, {
            originPort,
            destinationPort,
            bookingCount: 0,
            totalTEUs: 0,
            totalPrice: 0,
          });
        }

        const route = routeMap.get(routeKey)!;
        route.bookingCount++;
        route.totalPrice += rateQuote.pricing.totalAmount;

        // Calculate TEUs
        const teus = booking.containers.reduce((total, container) => {
          const teu = container.type.startsWith('20') ? 1 : 2;
          return total + teu;
        }, 0);
        route.totalTEUs += teus;
      } catch (error) {
        continue;
      }
    }

    // Convert to array and sort by booking count
    const tradeLanes: TopTradeLane[] = Array.from(routeMap.entries()).map(([route, data]) => ({
      route,
      originPort: data.originPort,
      destinationPort: data.destinationPort,
      bookingCount: data.bookingCount,
      totalTEUs: data.totalTEUs,
      avgPrice: data.totalPrice / data.bookingCount,
    }));

    // Sort by booking count and return top 5
    return tradeLanes.sort((a, b) => b.bookingCount - a.bookingCount).slice(0, 5);
  }

  /**
   * Get dashboard alerts
   */
  async getAlerts(organizationId: string): Promise<DashboardAlert[]> {
    const alerts: DashboardAlert[] = [];
    const allBookings = await this.bookingRepository.findByOrganization(organizationId);

    // Check for pending confirmations (older than 24h)
    const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
    const oldPendingBookings = allBookings.filter(
      b => b.status.value === 'pending_confirmation' && b.createdAt < oneDayAgo
    );

    for (const booking of oldPendingBookings) {
      alerts.push({
        id: `pending-${booking.id}`,
        type: 'confirmation',
        severity: 'medium',
        title: 'Pending Confirmation',
        message: `Booking ${booking.bookingNumber.value} is awaiting carrier confirmation for over 24 hours`,
        bookingId: booking.id,
        bookingNumber: booking.bookingNumber.value,
        createdAt: booking.createdAt,
        isRead: false,
      });
    }

    // Check for bookings departing soon (within 7 days) with pending status
    const sevenDaysFromNow = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
    for (const booking of allBookings) {
      try {
        const rateQuote = await this.rateQuoteRepository.findById(booking.rateQuoteId);
        if (rateQuote && rateQuote.route.length > 0) {
          const etd = rateQuote.route[0].departure;
          if (etd) {
            const etdDate = new Date(etd);
            if (
              etdDate <= sevenDaysFromNow &&
              etdDate >= new Date() &&
              booking.status.value === 'pending_confirmation'
            ) {
              alerts.push({
                id: `departure-${booking.id}`,
                type: 'delay',
                severity: 'high',
                title: 'Departure Soon - Not Confirmed',
                message: `Booking ${booking.bookingNumber.value} departs in ${Math.ceil(
                  (etdDate.getTime() - Date.now()) / (24 * 60 * 60 * 1000)
                )} days but is not confirmed yet`,
                bookingId: booking.id,
                bookingNumber: booking.bookingNumber.value,
                createdAt: booking.createdAt,
                isRead: false,
              });
            }
          }
        }
      } catch (error) {
        continue;
      }
    }

    // Sort by severity (critical > high > medium > low)
    const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
    alerts.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);

    return alerts;
  }

  /**
   * Get CSV Booking KPIs
   */
  async getCsvBookingKPIs(organizationId: string): Promise<CsvBookingKPIs> {
    const allCsvBookings = await this.csvBookingRepository.findByOrganizationId(organizationId);

    const now = new Date();
    const thisMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);

    // Filter by status
    const acceptedBookings = allCsvBookings.filter(
      (b: CsvBooking) => b.status === CsvBookingStatus.ACCEPTED
    );
    const rejectedBookings = allCsvBookings.filter(
      (b: CsvBooking) => b.status === CsvBookingStatus.REJECTED
    );
    const pendingBookings = allCsvBookings.filter(
      (b: CsvBooking) => b.status === CsvBookingStatus.PENDING
    );

    // This month stats
    const acceptedThisMonth = acceptedBookings.filter(
      (b: CsvBooking) => b.requestedAt >= thisMonthStart
    ).length;
    const rejectedThisMonth = rejectedBookings.filter(
      (b: CsvBooking) => b.requestedAt >= thisMonthStart
    ).length;

    // Calculate total weight and volume for accepted bookings
    const totalWeightAcceptedKG = acceptedBookings.reduce(
      (sum: number, b: CsvBooking) => sum + b.weightKG,
      0
    );
    const totalVolumeAcceptedCBM = acceptedBookings.reduce(
      (sum: number, b: CsvBooking) => sum + b.volumeCBM,
      0
    );

    // Calculate acceptance rate
    const totalProcessed = acceptedBookings.length + rejectedBookings.length;
    const acceptanceRate =
      totalProcessed > 0 ? (acceptedBookings.length / totalProcessed) * 100 : 0;

    return {
      totalAccepted: acceptedBookings.length,
      totalRejected: rejectedBookings.length,
      totalPending: pendingBookings.length,
      totalWeightAcceptedKG,
      totalVolumeAcceptedCBM,
      acceptanceRate,
      acceptedThisMonth,
      rejectedThisMonth,
    };
  }

  /**
   * Get Top Carriers by booking count
   */
  async getTopCarriers(organizationId: string, limit: number = 5): Promise<TopCarrier[]> {
    const allCsvBookings = await this.csvBookingRepository.findByOrganizationId(organizationId);

    // Group by carrier
    const carrierMap = new Map<
      string,
      {
        totalBookings: number;
        acceptedBookings: number;
        rejectedBookings: number;
        totalWeightKG: number;
        totalVolumeCBM: number;
        totalPriceUSD: number;
      }
    >();

    for (const booking of allCsvBookings) {
      const carrierName = booking.carrierName;

      if (!carrierMap.has(carrierName)) {
        carrierMap.set(carrierName, {
          totalBookings: 0,
          acceptedBookings: 0,
          rejectedBookings: 0,
          totalWeightKG: 0,
          totalVolumeCBM: 0,
          totalPriceUSD: 0,
        });
      }

      const carrier = carrierMap.get(carrierName)!;
      carrier.totalBookings++;

      if (booking.status === CsvBookingStatus.ACCEPTED) {
        carrier.acceptedBookings++;
        carrier.totalWeightKG += booking.weightKG;
        carrier.totalVolumeCBM += booking.volumeCBM;
      }

      if (booking.status === CsvBookingStatus.REJECTED) {
        carrier.rejectedBookings++;
      }

      // Add price (prefer USD, fallback to EUR converted)
      if (booking.priceUSD) {
        carrier.totalPriceUSD += booking.priceUSD;
      } else if (booking.priceEUR) {
        // Simple EUR to USD conversion (1.1 rate) - in production, use real exchange rate
        carrier.totalPriceUSD += booking.priceEUR * 1.1;
      }
    }

    // Convert to array
    const topCarriers: TopCarrier[] = Array.from(carrierMap.entries()).map(
      ([carrierName, data]) => ({
        carrierName,
        totalBookings: data.totalBookings,
        acceptedBookings: data.acceptedBookings,
        rejectedBookings: data.rejectedBookings,
        acceptanceRate:
          data.totalBookings > 0 ? (data.acceptedBookings / data.totalBookings) * 100 : 0,
        totalWeightKG: data.totalWeightKG,
        totalVolumeCBM: data.totalVolumeCBM,
        avgPriceUSD: data.totalBookings > 0 ? data.totalPriceUSD / data.totalBookings : 0,
      })
    );

    // Sort by total bookings (most bookings first)
    return topCarriers.sort((a, b) => b.totalBookings - a.totalBookings).slice(0, limit);
  }
}
