/**
 * PlantLineageAgent
 * Monitors and manages plant lineage tracking in the blockchain
 *
 * Responsibilities:
 * - Validate new plant registrations
 * - Track generation lineage and ancestry
 * - Detect and alert on anomalies in plant data
 * - Generate lineage reports and family trees
 * - Monitor plant status transitions
 */

import { BaseAgent } from './BaseAgent';
import { AgentConfig, AgentTask } from './types';
import { getBlockchain } from '../blockchain/manager';
import { PlantData, PlantBlock, PropagationType } from '../blockchain/types';

interface LineageAnalysis {
  plantId: string;
  generation: number;
  ancestors: string[];
  descendants: string[];
  totalLineageSize: number;
  propagationChain: PropagationType[];
  geographicSpread: number; // km
  oldestAncestorDate: string;
  healthScore: number;
}

interface LineageAnomaly {
  type: 'orphan' | 'circular' | 'invalid_generation' | 'missing_parent' | 'suspicious_location';
  plantId: string;
  description: string;
  severity: 'low' | 'medium' | 'high';
}

export class PlantLineageAgent extends BaseAgent {
  private lineageCache: Map<string, LineageAnalysis> = new Map();
  private anomalyLog: LineageAnomaly[] = [];
  private lastFullScanAt: string | null = null;

  constructor() {
    const config: AgentConfig = {
      id: 'plant-lineage-agent',
      name: 'Plant Lineage Agent',
      description: 'Monitors plant lineage integrity and generates ancestry reports',
      enabled: true,
      intervalMs: 60000, // Run every minute
      priority: 'high',
      maxRetries: 3,
      timeoutMs: 30000
    };
    super(config);
  }

  /**
   * Main execution cycle
   */
  async runOnce(): Promise<AgentTask | null> {
    const blockchain = getBlockchain();
    const chain = blockchain.getChain();

    // Skip genesis block
    const plantBlocks = chain.slice(1);
    let processedCount = 0;
    let anomaliesFound = 0;

    for (const block of plantBlocks) {
      const plant = block.plant;

      // Analyze lineage if not cached or stale
      if (!this.lineageCache.has(plant.id)) {
        const analysis = this.analyzeLineage(plant.id, chain);
        this.lineageCache.set(plant.id, analysis);
      }

      // Check for anomalies
      const anomalies = this.detectAnomalies(block, chain);
      for (const anomaly of anomalies) {
        this.anomalyLog.push(anomaly);
        anomaliesFound++;

        if (anomaly.severity === 'high') {
          this.createAlert('warning', `Lineage Anomaly: ${anomaly.type}`,
            anomaly.description,
            { relatedEntityId: anomaly.plantId, relatedEntityType: 'plant' }
          );
        }
      }

      processedCount++;
    }

    // Monitor for plants needing attention
    this.monitorPlantHealth(chain);

    // Update scan timestamp
    this.lastFullScanAt = new Date().toISOString();

    // Keep anomaly log manageable
    if (this.anomalyLog.length > 1000) {
      this.anomalyLog = this.anomalyLog.slice(-500);
    }

    return this.createTaskResult('lineage_scan', 'completed', {
      plantsScanned: processedCount,
      anomaliesFound,
      cacheSize: this.lineageCache.size,
      timestamp: this.lastFullScanAt
    });
  }

  /**
   * Analyze complete lineage for a plant
   */
  private analyzeLineage(plantId: string, chain: PlantBlock[]): LineageAnalysis {
    const plant = chain.find(b => b.plant.id === plantId)?.plant;
    if (!plant) {
      return this.createEmptyLineageAnalysis(plantId);
    }

    const ancestors = this.findAncestors(plantId, chain);
    const descendants = this.findDescendants(plantId, chain);
    const propagationChain = this.buildPropagationChain(plantId, chain);
    const geographicSpread = this.calculateGeographicSpread(plantId, chain);
    const oldestAncestor = this.findOldestAncestor(plantId, chain);

    return {
      plantId,
      generation: plant.generation,
      ancestors,
      descendants,
      totalLineageSize: ancestors.length + descendants.length + 1,
      propagationChain,
      geographicSpread,
      oldestAncestorDate: oldestAncestor?.timestamp || plant.dateAcquired,
      healthScore: this.calculateHealthScore(plant, chain)
    };
  }

  /**
   * Find all ancestors recursively
   */
  private findAncestors(plantId: string, chain: PlantBlock[], visited: Set<string> = new Set()): string[] {
    if (visited.has(plantId)) return [];
    visited.add(plantId);

    const plant = chain.find(b => b.plant.id === plantId)?.plant;
    if (!plant || !plant.parentPlantId) return [];

    const ancestors = [plant.parentPlantId];
    const parentAncestors = this.findAncestors(plant.parentPlantId, chain, visited);
    return [...ancestors, ...parentAncestors];
  }

  /**
   * Find all descendants recursively
   */
  private findDescendants(plantId: string, chain: PlantBlock[], visited: Set<string> = new Set()): string[] {
    if (visited.has(plantId)) return [];
    visited.add(plantId);

    const plant = chain.find(b => b.plant.id === plantId)?.plant;
    if (!plant) return [];

    const descendants: string[] = [...(plant.childPlants || [])];

    for (const childId of plant.childPlants || []) {
      const childDescendants = this.findDescendants(childId, chain, visited);
      descendants.push(...childDescendants);
    }

    return descendants;
  }

  /**
   * Build propagation type chain from origin to plant
   */
  private buildPropagationChain(plantId: string, chain: PlantBlock[]): PropagationType[] {
    const result: PropagationType[] = [];
    let currentId: string | undefined = plantId;

    while (currentId) {
      const plant = chain.find(b => b.plant.id === currentId)?.plant;
      if (!plant) break;

      result.unshift(plant.propagationType);
      currentId = plant.parentPlantId;
    }

    return result;
  }

  /**
   * Calculate geographic spread of lineage in km
   */
  private calculateGeographicSpread(plantId: string, chain: PlantBlock[]): number {
    const lineagePlantIds = [
      plantId,
      ...this.findAncestors(plantId, chain),
      ...this.findDescendants(plantId, chain)
    ];

    const locations = lineagePlantIds
      .map(id => chain.find(b => b.plant.id === id)?.plant.location)
      .filter(loc => loc !== undefined);

    if (locations.length < 2) return 0;

    let maxDistance = 0;
    for (let i = 0; i < locations.length; i++) {
      for (let j = i + 1; j < locations.length; j++) {
        const distance = this.calculateHaversine(
          locations[i]!.latitude, locations[i]!.longitude,
          locations[j]!.latitude, locations[j]!.longitude
        );
        maxDistance = Math.max(maxDistance, distance);
      }
    }

    return Math.round(maxDistance * 100) / 100;
  }

  /**
   * Find oldest ancestor
   */
  private findOldestAncestor(plantId: string, chain: PlantBlock[]): PlantBlock | null {
    const ancestors = this.findAncestors(plantId, chain);
    if (ancestors.length === 0) return null;

    let oldest: PlantBlock | null = null;
    let oldestDate = new Date();

    for (const ancestorId of ancestors) {
      const block = chain.find(b => b.plant.id === ancestorId);
      if (block) {
        const date = new Date(block.timestamp);
        if (date < oldestDate) {
          oldestDate = date;
          oldest = block;
        }
      }
    }

    return oldest;
  }

  /**
   * Calculate health score for a plant (0-100)
   */
  private calculateHealthScore(plant: PlantData, chain: PlantBlock[]): number {
    let score = 100;

    // Deduct for status issues
    if (plant.status === 'deceased') score -= 50;
    if (plant.status === 'dormant') score -= 10;

    // Deduct for missing data
    if (!plant.environment) score -= 10;
    if (!plant.growthMetrics) score -= 10;

    // Deduct for high generation (genetic drift risk)
    if (plant.generation > 10) score -= Math.min(20, plant.generation - 10);

    // Bonus for having offspring (successful propagation)
    if (plant.childPlants && plant.childPlants.length > 0) {
      score += Math.min(10, plant.childPlants.length * 2);
    }

    return Math.max(0, Math.min(100, score));
  }

  /**
   * Detect anomalies in plant data
   */
  private detectAnomalies(block: PlantBlock, chain: PlantBlock[]): LineageAnomaly[] {
    const anomalies: LineageAnomaly[] = [];
    const plant = block.plant;

    // Check for orphan plants (non-original with missing parent)
    if (plant.propagationType !== 'original' && plant.parentPlantId) {
      const parent = chain.find(b => b.plant.id === plant.parentPlantId);
      if (!parent) {
        anomalies.push({
          type: 'missing_parent',
          plantId: plant.id,
          description: `Plant ${plant.id} references parent ${plant.parentPlantId} which doesn't exist`,
          severity: 'medium'
        });
      }
    }

    // Check for invalid generation numbers
    if (plant.parentPlantId) {
      const parent = chain.find(b => b.plant.id === plant.parentPlantId);
      if (parent && plant.generation !== parent.plant.generation + 1) {
        anomalies.push({
          type: 'invalid_generation',
          plantId: plant.id,
          description: `Generation ${plant.generation} doesn't match parent generation ${parent.plant.generation}`,
          severity: 'medium'
        });
      }
    }

    // Check for suspicious location jumps
    if (plant.parentPlantId) {
      const parent = chain.find(b => b.plant.id === plant.parentPlantId);
      if (parent) {
        const distance = this.calculateHaversine(
          plant.location.latitude, plant.location.longitude,
          parent.plant.location.latitude, parent.plant.location.longitude
        );
        // Flag if offspring is more than 1000km from parent
        if (distance > 1000) {
          anomalies.push({
            type: 'suspicious_location',
            plantId: plant.id,
            description: `Plant is ${Math.round(distance)}km from parent - verify transport records`,
            severity: 'low'
          });
        }
      }
    }

    // Check for circular references
    const ancestors = this.findAncestors(plant.id, chain);
    if (ancestors.includes(plant.id)) {
      anomalies.push({
        type: 'circular',
        plantId: plant.id,
        description: 'Circular lineage reference detected',
        severity: 'high'
      });
    }

    return anomalies;
  }

  /**
   * Monitor overall plant health across network
   */
  private monitorPlantHealth(chain: PlantBlock[]): void {
    const statusCounts: Record<string, number> = {};
    let deceasedCount = 0;
    let recentDeaths = 0;
    const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;

    for (const block of chain.slice(1)) {
      const status = block.plant.status;
      statusCounts[status] = (statusCounts[status] || 0) + 1;

      if (status === 'deceased') {
        deceasedCount++;
        if (new Date(block.timestamp).getTime() > oneWeekAgo) {
          recentDeaths++;
        }
      }
    }

    // Alert if death rate is high
    const totalPlants = chain.length - 1;
    if (totalPlants > 10 && recentDeaths / totalPlants > 0.1) {
      this.createAlert('warning', 'High Plant Mortality Rate',
        `${recentDeaths} plants marked as deceased in the last week (${Math.round(recentDeaths / totalPlants * 100)}% of network)`,
        { actionRequired: 'Investigate potential environmental or disease issues' }
      );
    }
  }

  /**
   * Calculate Haversine distance
   */
  private calculateHaversine(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371; // km
    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;
  }

  /**
   * Create empty lineage analysis
   */
  private createEmptyLineageAnalysis(plantId: string): LineageAnalysis {
    return {
      plantId,
      generation: 0,
      ancestors: [],
      descendants: [],
      totalLineageSize: 1,
      propagationChain: [],
      geographicSpread: 0,
      oldestAncestorDate: new Date().toISOString(),
      healthScore: 0
    };
  }

  /**
   * Get lineage analysis for a specific plant
   */
  getLineageAnalysis(plantId: string): LineageAnalysis | null {
    return this.lineageCache.get(plantId) || null;
  }

  /**
   * Get all detected anomalies
   */
  getAnomalies(): LineageAnomaly[] {
    return this.anomalyLog;
  }

  /**
   * Get network statistics
   */
  getNetworkStats(): {
    totalPlants: number;
    totalLineages: number;
    avgGenerationDepth: number;
    avgLineageSize: number;
    geographicSpread: number;
  } {
    const analyses = Array.from(this.lineageCache.values());
    const totalPlants = analyses.length;

    if (totalPlants === 0) {
      return {
        totalPlants: 0,
        totalLineages: 0,
        avgGenerationDepth: 0,
        avgLineageSize: 0,
        geographicSpread: 0
      };
    }

    const rootPlants = analyses.filter(a => a.ancestors.length === 0);
    const avgGen = analyses.reduce((sum, a) => sum + a.generation, 0) / totalPlants;
    const avgSize = analyses.reduce((sum, a) => sum + a.totalLineageSize, 0) / totalPlants;
    const maxSpread = Math.max(...analyses.map(a => a.geographicSpread));

    return {
      totalPlants,
      totalLineages: rootPlants.length,
      avgGenerationDepth: Math.round(avgGen * 10) / 10,
      avgLineageSize: Math.round(avgSize * 10) / 10,
      geographicSpread: maxSpread
    };
  }
}

// Singleton instance
let lineageAgentInstance: PlantLineageAgent | null = null;

export function getPlantLineageAgent(): PlantLineageAgent {
  if (!lineageAgentInstance) {
    lineageAgentInstance = new PlantLineageAgent();
  }
  return lineageAgentInstance;
}
