/**
 * MarketMatchingAgent
 * Connects grower supply with consumer demand
 *
 * Responsibilities:
 * - Match supply commitments with demand signals
 * - Optimize delivery routes and logistics
 * - Facilitate fair pricing
 * - Track match success rates
 * - Enable local food distribution
 */

import { BaseAgent } from './BaseAgent';
import { AgentConfig, AgentTask } from './types';
import { getDemandForecaster } from '../demand/forecaster';
import { getTransportChain } from '../transport/tracker';

interface SupplyOffer {
  id: string;
  growerId: string;
  growerName: string;
  produceType: string;
  availableKg: number;
  pricePerKg: number;
  location: { latitude: number; longitude: number };
  availableFrom: string;
  availableUntil: string;
  certifications: string[];
  deliveryRadius: number;
  qualityGrade: 'premium' | 'standard' | 'economy';
}

interface DemandRequest {
  id: string;
  consumerId: string;
  produceType: string;
  requestedKg: number;
  maxPricePerKg: number;
  location: { latitude: number; longitude: number };
  neededBy: string;
  certificationRequirements: string[];
  flexibleOnQuantity: boolean;
  flexibleOnTiming: boolean;
}

interface MarketMatch {
  id: string;
  supplyId: string;
  demandId: string;
  growerId: string;
  consumerId: string;
  produceType: string;
  matchedQuantityKg: number;
  agreedPricePerKg: number;
  deliveryDistanceKm: number;
  estimatedCarbonKg: number;
  matchScore: number;
  status: 'proposed' | 'accepted' | 'rejected' | 'fulfilled' | 'cancelled';
  createdAt: string;
  deliveryDate?: string;
  matchFactors: {
    priceScore: number;
    distanceScore: number;
    certificationScore: number;
    timingScore: number;
  };
}

interface MarketStats {
  totalMatches: number;
  successfulMatches: number;
  totalVolumeKg: number;
  totalRevenue: number;
  avgDistanceKm: number;
  avgCarbonSavedKg: number;
  matchSuccessRate: number;
  topProduceTypes: { type: string; volumeKg: number }[];
}

interface PricingAnalysis {
  produceType: string;
  avgPrice: number;
  minPrice: number;
  maxPrice: number;
  priceRange: 'stable' | 'moderate' | 'volatile';
  recommendedPrice: number;
  demandPressure: 'low' | 'medium' | 'high';
}

export class MarketMatchingAgent extends BaseAgent {
  private supplyOffers: Map<string, SupplyOffer> = new Map();
  private demandRequests: Map<string, DemandRequest> = new Map();
  private matches: Map<string, MarketMatch> = new Map();
  private pricingData: Map<string, PricingAnalysis> = new Map();
  private marketStats: MarketStats;

  constructor() {
    const config: AgentConfig = {
      id: 'market-matching-agent',
      name: 'Market Matching Agent',
      description: 'Connects supply with demand for local food distribution',
      enabled: true,
      intervalMs: 60000, // Run every minute
      priority: 'high',
      maxRetries: 3,
      timeoutMs: 30000
    };
    super(config);

    this.marketStats = {
      totalMatches: 0,
      successfulMatches: 0,
      totalVolumeKg: 0,
      totalRevenue: 0,
      avgDistanceKm: 0,
      avgCarbonSavedKg: 0,
      matchSuccessRate: 0,
      topProduceTypes: []
    };
  }

  /**
   * Main execution cycle
   */
  async runOnce(): Promise<AgentTask | null> {
    // Clean up expired offers and requests
    this.cleanupExpired();

    // Find potential matches
    const newMatches = this.findMatches();

    // Update pricing analysis
    this.updatePricingAnalysis();

    // Update market statistics
    this.updateMarketStats();

    // Generate alerts for unmatched supply/demand
    this.checkUnmatchedAlerts();

    return this.createTaskResult('market_matching', 'completed', {
      activeSupplyOffers: this.supplyOffers.size,
      activeDemandRequests: this.demandRequests.size,
      newMatchesFound: newMatches.length,
      totalActiveMatches: this.matches.size,
      marketStats: this.marketStats
    });
  }

  /**
   * Register a supply offer
   */
  registerSupplyOffer(offer: SupplyOffer): void {
    this.supplyOffers.set(offer.id, offer);
  }

  /**
   * Register a demand request
   */
  registerDemandRequest(request: DemandRequest): void {
    this.demandRequests.set(request.id, request);
  }

  /**
   * Find potential matches between supply and demand
   */
  private findMatches(): MarketMatch[] {
    const newMatches: MarketMatch[] = [];

    for (const [supplyId, supply] of this.supplyOffers) {
      // Check if supply already has full matches
      const existingMatches = Array.from(this.matches.values())
        .filter(m => m.supplyId === supplyId && m.status !== 'rejected' && m.status !== 'cancelled');

      const matchedQuantity = existingMatches.reduce((sum, m) => sum + m.matchedQuantityKg, 0);
      const remainingSupply = supply.availableKg - matchedQuantity;

      if (remainingSupply <= 0) continue;

      // Find matching demand requests
      for (const [demandId, demand] of this.demandRequests) {
        // Check if demand already matched
        const demandMatches = Array.from(this.matches.values())
          .filter(m => m.demandId === demandId && m.status !== 'rejected' && m.status !== 'cancelled');

        if (demandMatches.length > 0) continue;

        // Check produce type match
        if (supply.produceType.toLowerCase() !== demand.produceType.toLowerCase()) continue;

        // Check price compatibility
        if (supply.pricePerKg > demand.maxPricePerKg) continue;

        // Check delivery radius
        const distance = this.calculateDistance(supply.location, demand.location);
        if (distance > supply.deliveryRadius) continue;

        // Check timing
        const supplyAvailable = new Date(supply.availableFrom);
        const demandNeeded = new Date(demand.neededBy);
        if (supplyAvailable > demandNeeded) continue;

        // Check certifications
        const certsMet = demand.certificationRequirements.every(
          cert => supply.certifications.includes(cert)
        );
        if (!certsMet) continue;

        // Calculate match score
        const matchScore = this.calculateMatchScore(supply, demand, distance);

        // Calculate matched quantity
        const matchedQty = Math.min(remainingSupply, demand.requestedKg);

        // Estimate carbon footprint
        const carbonKg = this.estimateCarbonFootprint(distance, matchedQty);

        // Create match
        const match: MarketMatch = {
          id: `match-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
          supplyId,
          demandId,
          growerId: supply.growerId,
          consumerId: demand.consumerId,
          produceType: supply.produceType,
          matchedQuantityKg: matchedQty,
          agreedPricePerKg: this.calculateFairPrice(supply.pricePerKg, demand.maxPricePerKg),
          deliveryDistanceKm: Math.round(distance * 100) / 100,
          estimatedCarbonKg: carbonKg,
          matchScore,
          status: 'proposed',
          createdAt: new Date().toISOString(),
          matchFactors: {
            priceScore: this.calculatePriceScore(supply.pricePerKg, demand.maxPricePerKg),
            distanceScore: this.calculateDistanceScore(distance),
            certificationScore: certsMet ? 100 : 0,
            timingScore: this.calculateTimingScore(supplyAvailable, demandNeeded)
          }
        };

        this.matches.set(match.id, match);
        newMatches.push(match);

        // Alert for high-value matches
        if (matchScore >= 90 && matchedQty >= 10) {
          this.createAlert('info', 'High-Quality Match Found',
            `${matchedQty}kg of ${supply.produceType} matched between grower and consumer`,
            { relatedEntityId: match.id, relatedEntityType: 'match' }
          );
        }
      }
    }

    return newMatches;
  }

  /**
   * Calculate match score (0-100)
   */
  private calculateMatchScore(supply: SupplyOffer, demand: DemandRequest, distance: number): number {
    let score = 0;

    // Price score (30 points max)
    const priceRatio = supply.pricePerKg / demand.maxPricePerKg;
    score += Math.max(0, 30 * (1 - priceRatio));

    // Distance score (25 points max) - shorter is better
    score += Math.max(0, 25 * (1 - distance / 100));

    // Quantity match score (20 points max)
    const qtyRatio = Math.min(supply.availableKg, demand.requestedKg) /
      Math.max(supply.availableKg, demand.requestedKg);
    score += 20 * qtyRatio;

    // Quality score (15 points max)
    const qualityPoints: Record<string, number> = { premium: 15, standard: 10, economy: 5 };
    score += qualityPoints[supply.qualityGrade] || 5;

    // Certification match (10 points max)
    if (supply.certifications.length > 0) {
      const certMatch = demand.certificationRequirements.filter(
        cert => supply.certifications.includes(cert)
      ).length / Math.max(1, demand.certificationRequirements.length);
      score += 10 * certMatch;
    } else if (demand.certificationRequirements.length === 0) {
      score += 10;
    }

    return Math.round(Math.min(100, score));
  }

  /**
   * Calculate fair price between supply and demand
   */
  private calculateFairPrice(supplyPrice: number, maxDemandPrice: number): number {
    // Weighted average favoring supply price slightly
    return Math.round((supplyPrice * 0.6 + maxDemandPrice * 0.4) * 100) / 100;
  }

  /**
   * Calculate price score
   */
  private calculatePriceScore(supplyPrice: number, maxDemandPrice: number): number {
    if (supplyPrice >= maxDemandPrice) return 0;
    return Math.round((1 - supplyPrice / maxDemandPrice) * 100);
  }

  /**
   * Calculate distance score
   */
  private calculateDistanceScore(distance: number): number {
    // 100 points for 0km, 0 points for 100km+
    return Math.max(0, Math.round(100 * (1 - distance / 100)));
  }

  /**
   * Calculate timing score
   */
  private calculateTimingScore(available: Date, needed: Date): number {
    const daysUntilNeeded = (needed.getTime() - available.getTime()) / (24 * 60 * 60 * 1000);
    if (daysUntilNeeded < 0) return 0;
    if (daysUntilNeeded > 14) return 50;
    return Math.round(100 * (1 - daysUntilNeeded / 14));
  }

  /**
   * Estimate carbon footprint for delivery
   */
  private estimateCarbonFootprint(distanceKm: number, weightKg: number): number {
    // Assume local electric vehicle: 0.02 kg CO2 per km per kg
    return Math.round(0.02 * distanceKm * weightKg * 100) / 100;
  }

  /**
   * Calculate Haversine distance
   */
  private calculateDistance(
    loc1: { latitude: number; longitude: number },
    loc2: { latitude: number; longitude: number }
  ): number {
    const R = 6371; // km
    const dLat = (loc2.latitude - loc1.latitude) * Math.PI / 180;
    const dLon = (loc2.longitude - loc1.longitude) * Math.PI / 180;
    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(loc1.latitude * Math.PI / 180) * Math.cos(loc2.latitude * 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;
  }

  /**
   * Clean up expired offers and requests
   */
  private cleanupExpired(): void {
    const now = new Date();

    for (const [id, supply] of this.supplyOffers) {
      if (new Date(supply.availableUntil) < now) {
        this.supplyOffers.delete(id);
      }
    }

    for (const [id, demand] of this.demandRequests) {
      if (new Date(demand.neededBy) < now) {
        this.demandRequests.delete(id);
      }
    }
  }

  /**
   * Update pricing analysis
   */
  private updatePricingAnalysis(): void {
    const pricesByType = new Map<string, number[]>();

    for (const supply of this.supplyOffers.values()) {
      const prices = pricesByType.get(supply.produceType) || [];
      prices.push(supply.pricePerKg);
      pricesByType.set(supply.produceType, prices);
    }

    for (const [produceType, prices] of pricesByType) {
      if (prices.length === 0) continue;

      const avg = prices.reduce((a, b) => a + b, 0) / prices.length;
      const min = Math.min(...prices);
      const max = Math.max(...prices);
      const range = max - min;

      let priceRange: PricingAnalysis['priceRange'];
      if (range / avg < 0.1) priceRange = 'stable';
      else if (range / avg < 0.3) priceRange = 'moderate';
      else priceRange = 'volatile';

      // Count demand for this produce
      const demandCount = Array.from(this.demandRequests.values())
        .filter(d => d.produceType.toLowerCase() === produceType.toLowerCase())
        .length;

      let demandPressure: PricingAnalysis['demandPressure'];
      if (demandCount > prices.length * 2) demandPressure = 'high';
      else if (demandCount > prices.length) demandPressure = 'medium';
      else demandPressure = 'low';

      this.pricingData.set(produceType, {
        produceType,
        avgPrice: Math.round(avg * 100) / 100,
        minPrice: Math.round(min * 100) / 100,
        maxPrice: Math.round(max * 100) / 100,
        priceRange,
        recommendedPrice: Math.round((avg + (demandPressure === 'high' ? avg * 0.1 : 0)) * 100) / 100,
        demandPressure
      });
    }
  }

  /**
   * Update market statistics
   */
  private updateMarketStats(): void {
    const allMatches = Array.from(this.matches.values());
    const successful = allMatches.filter(m => m.status === 'fulfilled');

    const volumeByType = new Map<string, number>();

    let totalDistance = 0;
    let totalCarbon = 0;
    let totalRevenue = 0;

    for (const match of successful) {
      volumeByType.set(
        match.produceType,
        (volumeByType.get(match.produceType) || 0) + match.matchedQuantityKg
      );

      totalDistance += match.deliveryDistanceKm;
      totalCarbon += match.estimatedCarbonKg;
      totalRevenue += match.matchedQuantityKg * match.agreedPricePerKg;
    }

    const topProduceTypes = Array.from(volumeByType.entries())
      .map(([type, volumeKg]) => ({ type, volumeKg }))
      .sort((a, b) => b.volumeKg - a.volumeKg)
      .slice(0, 5);

    this.marketStats = {
      totalMatches: allMatches.length,
      successfulMatches: successful.length,
      totalVolumeKg: Math.round(successful.reduce((sum, m) => sum + m.matchedQuantityKg, 0) * 10) / 10,
      totalRevenue: Math.round(totalRevenue * 100) / 100,
      avgDistanceKm: successful.length > 0 ? Math.round(totalDistance / successful.length * 10) / 10 : 0,
      avgCarbonSavedKg: successful.length > 0 ? Math.round(totalCarbon / successful.length * 100) / 100 : 0,
      matchSuccessRate: allMatches.length > 0
        ? Math.round(successful.length / allMatches.length * 100)
        : 0,
      topProduceTypes
    };
  }

  /**
   * Check for unmatched supply/demand alerts
   */
  private checkUnmatchedAlerts(): void {
    // Alert for supply that's been available for > 3 days without matches
    const threeDaysAgo = Date.now() - 3 * 24 * 60 * 60 * 1000;

    for (const supply of this.supplyOffers.values()) {
      const hasMatches = Array.from(this.matches.values())
        .some(m => m.supplyId === supply.id);

      if (!hasMatches && new Date(supply.availableFrom).getTime() < threeDaysAgo) {
        this.createAlert('warning', 'Unmatched Supply',
          `${supply.availableKg}kg of ${supply.produceType} from ${supply.growerName} has no matches`,
          {
            actionRequired: 'Consider adjusting price or expanding delivery radius',
            relatedEntityId: supply.id,
            relatedEntityType: 'supply'
          }
        );
      }
    }

    // Alert for urgent demand without matches
    const oneDayFromNow = Date.now() + 24 * 60 * 60 * 1000;

    for (const demand of this.demandRequests.values()) {
      const hasMatches = Array.from(this.matches.values())
        .some(m => m.demandId === demand.id);

      if (!hasMatches && new Date(demand.neededBy).getTime() < oneDayFromNow) {
        this.createAlert('warning', 'Urgent Unmatched Demand',
          `${demand.requestedKg}kg of ${demand.produceType} needed within 24 hours has no matches`,
          {
            actionRequired: 'Expand search radius or consider alternatives',
            relatedEntityId: demand.id,
            relatedEntityType: 'demand'
          }
        );
      }
    }
  }

  /**
   * Accept a match
   */
  acceptMatch(matchId: string): boolean {
    const match = this.matches.get(matchId);
    if (!match || match.status !== 'proposed') return false;

    match.status = 'accepted';
    match.deliveryDate = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString();
    return true;
  }

  /**
   * Fulfill a match
   */
  fulfillMatch(matchId: string): boolean {
    const match = this.matches.get(matchId);
    if (!match || match.status !== 'accepted') return false;

    match.status = 'fulfilled';
    this.marketStats.successfulMatches++;
    return true;
  }

  /**
   * Get match by ID
   */
  getMatch(matchId: string): MarketMatch | null {
    return this.matches.get(matchId) || null;
  }

  /**
   * Get all matches
   */
  getAllMatches(): MarketMatch[] {
    return Array.from(this.matches.values());
  }

  /**
   * Get matches for a grower
   */
  getGrowerMatches(growerId: string): MarketMatch[] {
    return Array.from(this.matches.values())
      .filter(m => m.growerId === growerId);
  }

  /**
   * Get matches for a consumer
   */
  getConsumerMatches(consumerId: string): MarketMatch[] {
    return Array.from(this.matches.values())
      .filter(m => m.consumerId === consumerId);
  }

  /**
   * Get pricing analysis
   */
  getPricingAnalysis(produceType?: string): PricingAnalysis[] {
    if (produceType) {
      const analysis = this.pricingData.get(produceType);
      return analysis ? [analysis] : [];
    }
    return Array.from(this.pricingData.values());
  }

  /**
   * Get market stats
   */
  getMarketStats(): MarketStats {
    return this.marketStats;
  }
}

// Singleton instance
let marketAgentInstance: MarketMatchingAgent | null = null;

export function getMarketMatchingAgent(): MarketMatchingAgent {
  if (!marketAgentInstance) {
    marketAgentInstance = new MarketMatchingAgent();
  }
  return marketAgentInstance;
}
