/**
 * Demand Forecaster for LocalGreenChain
 * Aggregates consumer preferences and generates planting recommendations
 */

import {
  ConsumerPreference,
  DemandSignal,
  DemandItem,
  PlantingRecommendation,
  DemandForecast,
  ProduceForecast,
  SupplyCommitment,
  MarketMatch,
  SeasonalPlan,
  ProduceCategory,
  RiskFactor
} from './types';

// Seasonal growing data for common produce
const SEASONAL_DATA: Record<string, {
  categories: ProduceCategory[];
  growingDays: number;
  seasons: ('spring' | 'summer' | 'fall' | 'winter')[];
  yieldPerSqm: number; // kg per sq meter
  idealTemp: { min: number; max: number };
}> = {
  'lettuce': {
    categories: ['leafy_greens'],
    growingDays: 45,
    seasons: ['spring', 'fall'],
    yieldPerSqm: 4,
    idealTemp: { min: 10, max: 21 }
  },
  'tomato': {
    categories: ['nightshades', 'fruits'],
    growingDays: 80,
    seasons: ['summer'],
    yieldPerSqm: 8,
    idealTemp: { min: 18, max: 29 }
  },
  'spinach': {
    categories: ['leafy_greens'],
    growingDays: 40,
    seasons: ['spring', 'fall', 'winter'],
    yieldPerSqm: 3,
    idealTemp: { min: 5, max: 18 }
  },
  'kale': {
    categories: ['leafy_greens', 'brassicas'],
    growingDays: 55,
    seasons: ['spring', 'fall', 'winter'],
    yieldPerSqm: 3.5,
    idealTemp: { min: 5, max: 24 }
  },
  'basil': {
    categories: ['herbs'],
    growingDays: 30,
    seasons: ['spring', 'summer'],
    yieldPerSqm: 2,
    idealTemp: { min: 18, max: 29 }
  },
  'microgreens': {
    categories: ['microgreens'],
    growingDays: 14,
    seasons: ['spring', 'summer', 'fall', 'winter'],
    yieldPerSqm: 1.5,
    idealTemp: { min: 18, max: 24 }
  },
  'cucumber': {
    categories: ['squash'],
    growingDays: 60,
    seasons: ['summer'],
    yieldPerSqm: 10,
    idealTemp: { min: 18, max: 30 }
  },
  'pepper': {
    categories: ['nightshades'],
    growingDays: 75,
    seasons: ['summer'],
    yieldPerSqm: 6,
    idealTemp: { min: 18, max: 29 }
  },
  'carrot': {
    categories: ['root_vegetables'],
    growingDays: 70,
    seasons: ['spring', 'fall'],
    yieldPerSqm: 5,
    idealTemp: { min: 7, max: 24 }
  },
  'strawberry': {
    categories: ['berries', 'fruits'],
    growingDays: 90,
    seasons: ['spring', 'summer'],
    yieldPerSqm: 3,
    idealTemp: { min: 15, max: 26 }
  }
};

export class DemandForecaster {
  private preferences: Map<string, ConsumerPreference> = new Map();
  private supplyCommitments: Map<string, SupplyCommitment> = new Map();
  private demandSignals: Map<string, DemandSignal> = new Map();
  private marketMatches: Map<string, MarketMatch> = new Map();

  /**
   * Register consumer preference
   */
  registerPreference(preference: ConsumerPreference): void {
    this.preferences.set(preference.consumerId, preference);
  }

  /**
   * Register supply commitment from grower
   */
  registerSupply(commitment: SupplyCommitment): void {
    this.supplyCommitments.set(commitment.id, commitment);
  }

  /**
   * Generate demand signal for a region
   */
  generateDemandSignal(
    centerLat: number,
    centerLon: number,
    radiusKm: number,
    regionName: string,
    season: 'spring' | 'summer' | 'fall' | 'winter'
  ): DemandSignal {
    // Find consumers in region
    const regionalConsumers = Array.from(this.preferences.values()).filter(pref => {
      const distance = this.calculateDistance(
        centerLat, centerLon,
        pref.location.latitude, pref.location.longitude
      );
      return distance <= radiusKm;
    });

    // Aggregate demand by produce type
    const demandMap = new Map<string, {
      consumers: Set<string>;
      totalWeeklyKg: number;
      priorities: number[];
      certifications: Set<string>;
      prices: number[];
    }>();

    for (const consumer of regionalConsumers) {
      for (const item of consumer.preferredItems) {
        const existing = demandMap.get(item.produceType) || {
          consumers: new Set(),
          totalWeeklyKg: 0,
          priorities: [],
          certifications: new Set(),
          prices: []
        };

        existing.consumers.add(consumer.consumerId);

        // Calculate weekly demand based on household size
        const weeklyKg = (item.weeklyQuantity || 0.5) * consumer.householdSize;
        existing.totalWeeklyKg += weeklyKg;

        // Track priority
        const priorityValue = item.priority === 'must_have' ? 10 :
          item.priority === 'preferred' ? 7 :
          item.priority === 'nice_to_have' ? 4 : 2;
        existing.priorities.push(priorityValue);

        // Track certifications
        consumer.certificationPreferences.forEach(cert =>
          existing.certifications.add(cert)
        );

        // Track price expectations
        if (consumer.weeklyBudget && consumer.preferredItems.length > 0) {
          const avgPricePerItem = consumer.weeklyBudget / consumer.preferredItems.length;
          existing.prices.push(avgPricePerItem / weeklyKg);
        }

        demandMap.set(item.produceType, existing);
      }
    }

    // Convert to demand items
    const demandItems: DemandItem[] = Array.from(demandMap.entries()).map(([produceType, data]) => {
      const seasonalData = SEASONAL_DATA[produceType.toLowerCase()];
      const inSeason = seasonalData?.seasons.includes(season) ?? true;

      const avgPriority = data.priorities.length > 0
        ? data.priorities.reduce((a, b) => a + b, 0) / data.priorities.length
        : 5;

      const avgPrice = data.prices.length > 0
        ? data.prices.reduce((a, b) => a + b, 0) / data.prices.length
        : 5;

      return {
        produceType,
        category: seasonalData?.categories[0] || 'leafy_greens',
        weeklyDemandKg: data.totalWeeklyKg,
        monthlyDemandKg: data.totalWeeklyKg * 4,
        consumerCount: data.consumers.size,
        aggregatePriority: Math.round(avgPriority),
        urgency: avgPriority >= 8 ? 'immediate' :
          avgPriority >= 6 ? 'this_week' :
          avgPriority >= 4 ? 'this_month' : 'next_season',
        preferredCertifications: Array.from(data.certifications),
        averageWillingPrice: Math.round(avgPrice * 100) / 100,
        priceUnit: 'per_kg',
        inSeason,
        seasonalAvailability: {
          spring: seasonalData?.seasons.includes('spring') ?? true,
          summer: seasonalData?.seasons.includes('summer') ?? true,
          fall: seasonalData?.seasons.includes('fall') ?? true,
          winter: seasonalData?.seasons.includes('winter') ?? false
        },
        matchedSupply: 0,
        matchedGrowers: 0,
        gapKg: data.totalWeeklyKg
      };
    });

    // Calculate supply matching
    const regionalSupply = Array.from(this.supplyCommitments.values()).filter(supply =>
      supply.status === 'available' || supply.status === 'partially_committed'
    );

    for (const item of demandItems) {
      const matchingSupply = regionalSupply.filter(s =>
        s.produceType.toLowerCase() === item.produceType.toLowerCase()
      );

      item.matchedSupply = matchingSupply.reduce((sum, s) => sum + s.remainingKg, 0);
      item.matchedGrowers = matchingSupply.length;
      item.gapKg = Math.max(0, item.weeklyDemandKg - item.matchedSupply);
    }

    const totalWeeklyDemand = demandItems.reduce((sum, item) => sum + item.weeklyDemandKg, 0);
    const totalSupply = demandItems.reduce((sum, item) => sum + item.matchedSupply, 0);
    const totalGap = demandItems.reduce((sum, item) => sum + item.gapKg, 0);

    const signal: DemandSignal = {
      id: `demand-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      timestamp: new Date().toISOString(),
      region: {
        centerLat,
        centerLon,
        radiusKm,
        name: regionName
      },
      periodStart: new Date().toISOString(),
      periodEnd: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
      seasonalPeriod: season,
      demandItems: demandItems.sort((a, b) => b.aggregatePriority - a.aggregatePriority),
      totalConsumers: regionalConsumers.length,
      totalWeeklyDemandKg: totalWeeklyDemand,
      confidenceLevel: Math.min(100, regionalConsumers.length * 2),
      currentSupplyKg: totalSupply,
      supplyGapKg: totalGap,
      supplyStatus: totalGap <= 0 ? 'surplus' :
        totalGap < totalWeeklyDemand * 0.1 ? 'balanced' :
        totalGap < totalWeeklyDemand * 0.3 ? 'shortage' : 'critical'
    };

    this.demandSignals.set(signal.id, signal);
    return signal;
  }

  /**
   * Generate planting recommendations for a grower
   */
  generatePlantingRecommendations(
    growerId: string,
    growerLat: number,
    growerLon: number,
    deliveryRadiusKm: number,
    availableSpaceSqm: number,
    season: 'spring' | 'summer' | 'fall' | 'winter'
  ): PlantingRecommendation[] {
    const recommendations: PlantingRecommendation[] = [];

    // Find relevant demand signals
    const relevantSignals = Array.from(this.demandSignals.values()).filter(signal => {
      const distance = this.calculateDistance(
        growerLat, growerLon,
        signal.region.centerLat, signal.region.centerLon
      );
      return distance <= deliveryRadiusKm + signal.region.radiusKm &&
        signal.seasonalPeriod === season;
    });

    // Aggregate demand items across signals
    const aggregatedDemand = new Map<string, {
      totalGapKg: number;
      avgPrice: number;
      avgPriority: number;
      signalIds: string[];
    }>();

    for (const signal of relevantSignals) {
      for (const item of signal.demandItems) {
        if (item.gapKg > 0 && item.inSeason) {
          const existing = aggregatedDemand.get(item.produceType) || {
            totalGapKg: 0,
            avgPrice: 0,
            avgPriority: 0,
            signalIds: []
          };

          existing.totalGapKg += item.gapKg;
          existing.avgPrice = (existing.avgPrice * existing.signalIds.length + item.averageWillingPrice) /
            (existing.signalIds.length + 1);
          existing.avgPriority = (existing.avgPriority * existing.signalIds.length + item.aggregatePriority) /
            (existing.signalIds.length + 1);
          existing.signalIds.push(signal.id);

          aggregatedDemand.set(item.produceType, existing);
        }
      }
    }

    // Sort by opportunity score (gap * price * priority)
    const sortedOpportunities = Array.from(aggregatedDemand.entries())
      .map(([produceType, data]) => ({
        produceType,
        ...data,
        score: data.totalGapKg * data.avgPrice * data.avgPriority / 100
      }))
      .sort((a, b) => b.score - a.score);

    // Allocate space to top opportunities
    let remainingSpace = availableSpaceSqm;

    for (const opportunity of sortedOpportunities) {
      if (remainingSpace <= 0) break;

      const seasonalData = SEASONAL_DATA[opportunity.produceType.toLowerCase()];
      if (!seasonalData) continue;

      // Calculate space needed
      const yieldPerSqm = seasonalData.yieldPerSqm;
      const neededSpace = Math.min(
        remainingSpace,
        opportunity.totalGapKg / yieldPerSqm
      );

      if (neededSpace < 1) continue;

      const expectedYield = neededSpace * yieldPerSqm;
      const projectedRevenue = expectedYield * opportunity.avgPrice;

      // Assess risks
      const riskFactors: RiskFactor[] = [];

      if (seasonalData.seasons.length === 1) {
        riskFactors.push({
          type: 'weather',
          severity: 'medium',
          description: 'Single season crop with weather sensitivity',
          mitigationSuggestion: 'Consider greenhouse/vertical farm growing'
        });
      }

      if (opportunity.totalGapKg > expectedYield * 3) {
        riskFactors.push({
          type: 'market',
          severity: 'low',
          description: 'Strong demand exceeds your capacity',
          mitigationSuggestion: 'Consider partnering with other growers'
        });
      }

      if (opportunity.totalGapKg < expectedYield * 0.5) {
        riskFactors.push({
          type: 'oversupply',
          severity: 'medium',
          description: 'Risk of oversupply if demand doesn\'t grow',
          mitigationSuggestion: 'Start with smaller quantity and scale up'
        });
      }

      const overallRisk = riskFactors.some(r => r.severity === 'high') ? 'high' :
        riskFactors.some(r => r.severity === 'medium') ? 'medium' : 'low';

      const plantByDate = new Date();
      const harvestStart = new Date(plantByDate.getTime() + seasonalData.growingDays * 24 * 60 * 60 * 1000);
      const harvestEnd = new Date(harvestStart.getTime() + 21 * 24 * 60 * 60 * 1000);

      recommendations.push({
        id: `rec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        timestamp: new Date().toISOString(),
        growerId,
        produceType: opportunity.produceType,
        category: seasonalData.categories[0],
        recommendedQuantity: Math.round(neededSpace),
        quantityUnit: 'sqm',
        expectedYieldKg: Math.round(expectedYield * 10) / 10,
        yieldConfidence: 75,
        plantByDate: plantByDate.toISOString(),
        expectedHarvestStart: harvestStart.toISOString(),
        expectedHarvestEnd: harvestEnd.toISOString(),
        growingDays: seasonalData.growingDays,
        projectedDemandKg: opportunity.totalGapKg,
        projectedPricePerKg: Math.round(opportunity.avgPrice * 100) / 100,
        projectedRevenue: Math.round(projectedRevenue * 100) / 100,
        marketConfidence: Math.min(90, 50 + opportunity.signalIds.length * 10),
        riskFactors,
        overallRisk,
        demandSignalIds: opportunity.signalIds,
        explanation: `Based on ${opportunity.signalIds.length} demand signal(s) showing a gap of ${Math.round(opportunity.totalGapKg)}kg ` +
          `for ${opportunity.produceType}. With ${Math.round(neededSpace)} sqm, you can produce approximately ${Math.round(expectedYield)}kg ` +
          `at an expected price of $${opportunity.avgPrice.toFixed(2)}/kg.`
      });

      remainingSpace -= neededSpace;
    }

    return recommendations;
  }

  /**
   * Generate demand forecast
   */
  generateForecast(
    regionName: string,
    forecastWeeks: number = 12
  ): DemandForecast {
    const forecasts: ProduceForecast[] = [];

    // Get historical demand signals for the region
    const historicalSignals = Array.from(this.demandSignals.values())
      .filter(s => s.region.name === regionName)
      .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());

    // Aggregate by produce type
    const produceHistory = new Map<string, number[]>();

    for (const signal of historicalSignals) {
      for (const item of signal.demandItems) {
        const history = produceHistory.get(item.produceType) || [];
        history.push(item.weeklyDemandKg);
        produceHistory.set(item.produceType, history);
      }
    }

    // Generate forecasts
    for (const [produceType, history] of produceHistory) {
      if (history.length === 0) continue;

      const avgDemand = history.reduce((a, b) => a + b, 0) / history.length;
      const trend = history.length > 1
        ? (history[history.length - 1] - history[0]) / history.length
        : 0;

      const seasonalData = SEASONAL_DATA[produceType.toLowerCase()];
      const currentSeason = this.getCurrentSeason();
      const seasonalFactor = seasonalData?.seasons.includes(currentSeason) ? 1.2 : 0.6;

      const predictedDemand = (avgDemand + trend * forecastWeeks) * seasonalFactor;

      forecasts.push({
        produceType,
        category: seasonalData?.categories[0] || 'leafy_greens',
        predictedDemandKg: Math.round(predictedDemand * 10) / 10,
        confidenceInterval: {
          low: Math.round(predictedDemand * 0.7 * 10) / 10,
          high: Math.round(predictedDemand * 1.3 * 10) / 10
        },
        confidence: Math.min(95, 50 + history.length * 5),
        trend: trend > 0.1 ? 'increasing' : trend < -0.1 ? 'decreasing' : 'stable',
        trendStrength: Math.min(100, Math.abs(trend) * 100),
        seasonalFactor,
        predictedPricePerKg: 5, // Default price
        priceConfidenceInterval: { low: 3, high: 8 },
        factors: [
          {
            name: 'Seasonal adjustment',
            type: 'seasonal',
            impact: Math.round((seasonalFactor - 1) * 100),
            description: seasonalData?.seasons.includes(currentSeason)
              ? 'In season - higher demand expected'
              : 'Out of season - lower demand expected'
          },
          {
            name: 'Historical trend',
            type: 'trend',
            impact: Math.round(trend * 10),
            description: trend > 0 ? 'Growing popularity' : trend < 0 ? 'Declining interest' : 'Stable demand'
          }
        ]
      });
    }

    return {
      id: `forecast-${Date.now()}`,
      generatedAt: new Date().toISOString(),
      region: regionName,
      forecastPeriod: {
        start: new Date().toISOString(),
        end: new Date(Date.now() + forecastWeeks * 7 * 24 * 60 * 60 * 1000).toISOString()
      },
      forecasts: forecasts.sort((a, b) => b.predictedDemandKg - a.predictedDemandKg),
      modelVersion: '1.0.0',
      dataPointsUsed: historicalSignals.length,
      lastTrainingDate: new Date().toISOString()
    };
  }

  private getCurrentSeason(): 'spring' | 'summer' | 'fall' | 'winter' {
    const month = new Date().getMonth();
    if (month >= 2 && month <= 4) return 'spring';
    if (month >= 5 && month <= 7) return 'summer';
    if (month >= 8 && month <= 10) return 'fall';
    return 'winter';
  }

  private calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371;
    const dLat = (lat2 - lat1) * Math.PI / 180;
    const dLon = (lon2 - lon1) * Math.PI / 180;
    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * 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;
  }

  /**
   * Export state
   */
  toJSON(): object {
    return {
      preferences: Array.from(this.preferences.entries()),
      supplyCommitments: Array.from(this.supplyCommitments.entries()),
      demandSignals: Array.from(this.demandSignals.entries()),
      marketMatches: Array.from(this.marketMatches.entries())
    };
  }

  /**
   * Import state
   */
  static fromJSON(data: any): DemandForecaster {
    const forecaster = new DemandForecaster();
    if (data.preferences) {
      for (const [key, value] of data.preferences) {
        forecaster.preferences.set(key, value);
      }
    }
    if (data.supplyCommitments) {
      for (const [key, value] of data.supplyCommitments) {
        forecaster.supplyCommitments.set(key, value);
      }
    }
    if (data.demandSignals) {
      for (const [key, value] of data.demandSignals) {
        forecaster.demandSignals.set(key, value);
      }
    }
    if (data.marketMatches) {
      for (const [key, value] of data.marketMatches) {
        forecaster.marketMatches.set(key, value);
      }
    }
    return forecaster;
  }
}

// Singleton instance
let forecasterInstance: DemandForecaster | null = null;

export function getDemandForecaster(): DemandForecaster {
  if (!forecasterInstance) {
    forecasterInstance = new DemandForecaster();
  }
  return forecasterInstance;
}
