/**
 * Transport Tracker for LocalGreenChain
 * Manages seed-to-seed transport events and blockchain recording
 */

import crypto from 'crypto';
import {
  TransportEvent,
  TransportBlock,
  TransportLocation,
  TransportMethod,
  PlantJourney,
  EnvironmentalImpact,
  TransportQRData,
  CARBON_FACTORS,
  SeedAcquisitionEvent,
  PlantingEvent,
  GrowingTransportEvent,
  HarvestEvent,
  ProcessingEvent,
  DistributionEvent,
  ConsumerDeliveryEvent,
  SeedSavingEvent,
  SeedSharingEvent,
  TransportEventType
} from './types';

/**
 * TransportChain - Blockchain for transport events
 */
export class TransportChain {
  public chain: TransportBlock[];
  public difficulty: number;
  private eventIndex: Map<string, TransportBlock[]>;
  private plantEvents: Map<string, TransportEvent[]>;
  private batchEvents: Map<string, TransportEvent[]>;

  constructor(difficulty: number = 3) {
    this.chain = [this.createGenesisBlock()];
    this.difficulty = difficulty;
    this.eventIndex = new Map();
    this.plantEvents = new Map();
    this.batchEvents = new Map();
  }

  private createGenesisBlock(): TransportBlock {
    const genesisEvent: SeedAcquisitionEvent = {
      id: 'genesis-transport-0',
      timestamp: new Date().toISOString(),
      eventType: 'seed_acquisition',
      fromLocation: {
        latitude: 0,
        longitude: 0,
        locationType: 'seed_bank',
        facilityName: 'LocalGreenChain Genesis'
      },
      toLocation: {
        latitude: 0,
        longitude: 0,
        locationType: 'seed_bank',
        facilityName: 'LocalGreenChain Genesis'
      },
      distanceKm: 0,
      durationMinutes: 0,
      transportMethod: 'walking',
      carbonFootprintKg: 0,
      senderId: 'system',
      receiverId: 'system',
      status: 'verified',
      seedBatchId: 'genesis-seed-batch',
      sourceType: 'seed_bank',
      species: 'Blockchain primordialis',
      quantity: 1,
      quantityUnit: 'seeds',
      generation: 0
    };

    return {
      index: 0,
      timestamp: new Date().toISOString(),
      transportEvent: genesisEvent,
      previousHash: '0',
      hash: this.calculateHash(0, new Date().toISOString(), genesisEvent, '0', 0),
      nonce: 0,
      cumulativeCarbonKg: 0,
      cumulativeFoodMiles: 0,
      chainLength: 1
    };
  }

  private calculateHash(
    index: number,
    timestamp: string,
    event: TransportEvent,
    previousHash: string,
    nonce: number
  ): string {
    const data = `${index}${timestamp}${JSON.stringify(event)}${previousHash}${nonce}`;
    return crypto.createHash('sha256').update(data).digest('hex');
  }

  private mineBlock(block: TransportBlock): void {
    const target = '0'.repeat(this.difficulty);
    while (block.hash.substring(0, this.difficulty) !== target) {
      block.nonce++;
      block.hash = this.calculateHash(
        block.index,
        block.timestamp,
        block.transportEvent,
        block.previousHash,
        block.nonce
      );
    }
  }

  getLatestBlock(): TransportBlock {
    return this.chain[this.chain.length - 1];
  }

  /**
   * Record a new transport event
   */
  recordEvent(event: TransportEvent): TransportBlock {
    const latestBlock = this.getLatestBlock();

    // Calculate carbon footprint if not provided
    if (!event.carbonFootprintKg) {
      event.carbonFootprintKg = this.calculateCarbon(
        event.transportMethod,
        event.distanceKm,
        this.estimateWeight(event)
      );
    }

    const newBlock: TransportBlock = {
      index: this.chain.length,
      timestamp: new Date().toISOString(),
      transportEvent: event,
      previousHash: latestBlock.hash,
      hash: '',
      nonce: 0,
      cumulativeCarbonKg: latestBlock.cumulativeCarbonKg + event.carbonFootprintKg,
      cumulativeFoodMiles: latestBlock.cumulativeFoodMiles + event.distanceKm,
      chainLength: this.chain.length + 1
    };

    newBlock.hash = this.calculateHash(
      newBlock.index,
      newBlock.timestamp,
      event,
      newBlock.previousHash,
      newBlock.nonce
    );

    this.mineBlock(newBlock);
    this.chain.push(newBlock);
    this.indexEvent(event, newBlock);

    return newBlock;
  }

  private indexEvent(event: TransportEvent, block: TransportBlock): void {
    // Index by event ID
    const eventBlocks = this.eventIndex.get(event.id) || [];
    eventBlocks.push(block);
    this.eventIndex.set(event.id, eventBlocks);

    // Index by plant IDs
    const plantIds = this.extractPlantIds(event);
    for (const plantId of plantIds) {
      const events = this.plantEvents.get(plantId) || [];
      events.push(event);
      this.plantEvents.set(plantId, events);
    }

    // Index by batch IDs
    const batchIds = this.extractBatchIds(event);
    for (const batchId of batchIds) {
      const events = this.batchEvents.get(batchId) || [];
      events.push(event);
      this.batchEvents.set(batchId, events);
    }
  }

  private extractPlantIds(event: TransportEvent): string[] {
    switch (event.eventType) {
      case 'planting':
        return (event as PlantingEvent).plantIds;
      case 'growing_transport':
        return (event as GrowingTransportEvent).plantIds;
      case 'harvest':
        return (event as HarvestEvent).plantIds;
      case 'seed_saving':
        return (event as SeedSavingEvent).parentPlantIds;
      default:
        return [];
    }
  }

  private extractBatchIds(event: TransportEvent): string[] {
    const batchIds: string[] = [];

    switch (event.eventType) {
      case 'seed_acquisition':
        batchIds.push((event as SeedAcquisitionEvent).seedBatchId);
        break;
      case 'planting':
        batchIds.push((event as PlantingEvent).seedBatchId);
        break;
      case 'harvest':
        batchIds.push((event as HarvestEvent).harvestBatchId);
        if ((event as HarvestEvent).seedBatchIdCreated) {
          batchIds.push((event as HarvestEvent).seedBatchIdCreated!);
        }
        break;
      case 'processing':
        batchIds.push(...(event as ProcessingEvent).harvestBatchIds);
        batchIds.push((event as ProcessingEvent).processingBatchId);
        break;
      case 'distribution':
        batchIds.push(...(event as DistributionEvent).batchIds);
        break;
      case 'consumer_delivery':
        batchIds.push(...(event as ConsumerDeliveryEvent).batchIds);
        break;
      case 'seed_saving':
        batchIds.push((event as SeedSavingEvent).newSeedBatchId);
        break;
      case 'seed_sharing':
        batchIds.push((event as SeedSharingEvent).seedBatchId);
        break;
    }

    return batchIds;
  }

  private estimateWeight(event: TransportEvent): number {
    // Estimate weight in kg based on event type
    switch (event.eventType) {
      case 'seed_acquisition':
      case 'seed_saving':
      case 'seed_sharing':
        return 0.1; // Seeds are light
      case 'planting':
        return 0.5;
      case 'growing_transport':
        return 2;
      case 'harvest':
        return (event as HarvestEvent).netWeight || 5;
      case 'processing':
        return (event as ProcessingEvent).outputWeight || 5;
      case 'distribution':
      case 'consumer_delivery':
        return 5;
      default:
        return 1;
    }
  }

  /**
   * Calculate carbon footprint
   */
  calculateCarbon(method: TransportMethod, distanceKm: number, weightKg: number): number {
    const factor = CARBON_FACTORS[method] || 0.1;
    return factor * distanceKm * weightKg;
  }

  /**
   * Calculate distance between two locations using Haversine formula
   */
  static calculateDistance(from: TransportLocation, to: TransportLocation): number {
    const R = 6371; // Earth's radius in km
    const dLat = TransportChain.toRadians(to.latitude - from.latitude);
    const dLon = TransportChain.toRadians(to.longitude - from.longitude);

    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(TransportChain.toRadians(from.latitude)) *
      Math.cos(TransportChain.toRadians(to.latitude)) *
      Math.sin(dLon / 2) * Math.sin(dLon / 2);

    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  }

  private static toRadians(degrees: number): number {
    return degrees * (Math.PI / 180);
  }

  /**
   * Get complete journey for a plant
   */
  getPlantJourney(plantId: string): PlantJourney | null {
    const events = this.plantEvents.get(plantId);
    if (!events || events.length === 0) return null;

    // Sort events by timestamp
    const sortedEvents = [...events].sort(
      (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
    );

    const lastEvent = sortedEvents[sortedEvents.length - 1];

    // Calculate metrics
    let totalFoodMiles = 0;
    let totalCarbonKg = 0;
    let daysInTransit = 0;

    for (const event of sortedEvents) {
      totalFoodMiles += event.distanceKm;
      totalCarbonKg += event.carbonFootprintKg;
      daysInTransit += event.durationMinutes / (60 * 24);
    }

    // Find seed batch origin
    const seedAcquisition = sortedEvents.find(e => e.eventType === 'seed_acquisition') as SeedAcquisitionEvent | undefined;
    const planting = sortedEvents.find(e => e.eventType === 'planting') as PlantingEvent | undefined;

    // Calculate growing days
    const plantingDate = planting ? new Date(planting.timestamp) : null;
    const lastDate = new Date(lastEvent.timestamp);
    const daysGrowing = plantingDate
      ? Math.floor((lastDate.getTime() - plantingDate.getTime()) / (1000 * 60 * 60 * 24))
      : 0;

    // Determine current stage
    let currentStage: PlantJourney['currentStage'] = 'seed';
    if (sortedEvents.some(e => e.eventType === 'seed_saving')) {
      currentStage = 'seed_saving';
    } else if (sortedEvents.some(e => e.eventType === 'harvest')) {
      currentStage = 'post_harvest';
    } else if (sortedEvents.some(e => e.eventType === 'growing_transport')) {
      const lastGrowing = sortedEvents.filter(e => e.eventType === 'growing_transport').pop() as GrowingTransportEvent;
      currentStage = lastGrowing.plantStage;
    } else if (sortedEvents.some(e => e.eventType === 'planting')) {
      currentStage = 'seedling';
    }

    return {
      plantId,
      seedBatchOrigin: seedAcquisition?.seedBatchId || planting?.seedBatchId || 'unknown',
      currentCustodian: lastEvent.receiverId,
      currentLocation: lastEvent.toLocation,
      currentStage,
      events: sortedEvents,
      totalFoodMiles,
      totalCarbonKg,
      daysInTransit: Math.round(daysInTransit),
      daysGrowing,
      generation: seedAcquisition?.generation || 0,
      ancestorPlantIds: seedAcquisition?.parentPlantIds || [],
      descendantSeedBatches: sortedEvents
        .filter(e => e.eventType === 'seed_saving')
        .map(e => (e as SeedSavingEvent).newSeedBatchId)
    };
  }

  /**
   * Get environmental impact summary for a user
   */
  getEnvironmentalImpact(userId: string): EnvironmentalImpact {
    const userEvents = this.chain
      .filter(block =>
        block.transportEvent.senderId === userId ||
        block.transportEvent.receiverId === userId
      )
      .map(block => block.transportEvent);

    let totalCarbonKg = 0;
    let totalFoodMiles = 0;
    let totalWeight = 0;

    const breakdownByMethod: EnvironmentalImpact['breakdownByMethod'] = {} as any;
    const breakdownByEventType: EnvironmentalImpact['breakdownByEventType'] = {} as any;

    for (const event of userEvents) {
      totalCarbonKg += event.carbonFootprintKg;
      totalFoodMiles += event.distanceKm;
      totalWeight += this.estimateWeight(event);

      // Method breakdown
      if (!breakdownByMethod[event.transportMethod]) {
        breakdownByMethod[event.transportMethod] = { distance: 0, carbon: 0 };
      }
      breakdownByMethod[event.transportMethod].distance += event.distanceKm;
      breakdownByMethod[event.transportMethod].carbon += event.carbonFootprintKg;

      // Event type breakdown
      if (!breakdownByEventType[event.eventType]) {
        breakdownByEventType[event.eventType] = { count: 0, carbon: 0 };
      }
      breakdownByEventType[event.eventType].count++;
      breakdownByEventType[event.eventType].carbon += event.carbonFootprintKg;
    }

    // Conventional comparison (assume 1500 miles avg, 2.5 kg CO2/lb)
    const conventionalMiles = totalWeight * 1500;
    const conventionalCarbon = totalWeight * 2.5;

    return {
      totalCarbonKg,
      totalFoodMiles,
      carbonPerKgProduce: totalWeight > 0 ? totalCarbonKg / totalWeight : 0,
      milesPerKgProduce: totalWeight > 0 ? totalFoodMiles / totalWeight : 0,
      breakdownByMethod,
      breakdownByEventType,
      comparisonToConventional: {
        carbonSaved: Math.max(0, conventionalCarbon - totalCarbonKg),
        milesSaved: Math.max(0, conventionalMiles - totalFoodMiles),
        percentageReduction: conventionalCarbon > 0
          ? Math.round((1 - totalCarbonKg / conventionalCarbon) * 100)
          : 0
      }
    };
  }

  /**
   * Generate QR code data for a plant or batch
   */
  generateQRData(plantId?: string, batchId?: string): TransportQRData {
    const events = plantId
      ? this.plantEvents.get(plantId)
      : batchId
        ? this.batchEvents.get(batchId)
        : [];

    const lastEvent = events && events.length > 0
      ? events[events.length - 1]
      : null;

    const lineageHash = crypto.createHash('sha256')
      .update(JSON.stringify(events))
      .digest('hex')
      .substring(0, 16);

    return {
      plantId,
      batchId,
      blockchainAddress: this.getLatestBlock().hash.substring(0, 42),
      quickLookupUrl: `https://localgreenchain.org/track/${plantId || batchId}`,
      lineageHash,
      currentCustodian: lastEvent?.receiverId || 'unknown',
      lastEventType: lastEvent?.eventType || 'seed_acquisition',
      lastEventTimestamp: lastEvent?.timestamp || new Date().toISOString(),
      verificationCode: crypto.randomBytes(4).toString('hex').toUpperCase()
    };
  }

  /**
   * Verify chain integrity
   */
  isChainValid(): boolean {
    for (let i = 1; i < this.chain.length; i++) {
      const currentBlock = this.chain[i];
      const previousBlock = this.chain[i - 1];

      // Verify hash
      const expectedHash = this.calculateHash(
        currentBlock.index,
        currentBlock.timestamp,
        currentBlock.transportEvent,
        currentBlock.previousHash,
        currentBlock.nonce
      );

      if (currentBlock.hash !== expectedHash) {
        return false;
      }

      // Verify chain link
      if (currentBlock.previousHash !== previousBlock.hash) {
        return false;
      }
    }
    return true;
  }

  /**
   * Export to JSON
   */
  toJSON(): object {
    return {
      difficulty: this.difficulty,
      chain: this.chain
    };
  }

  /**
   * Import from JSON
   */
  static fromJSON(data: any): TransportChain {
    const chain = new TransportChain(data.difficulty);
    chain.chain = data.chain;

    // Rebuild indexes
    for (const block of chain.chain) {
      chain.indexEvent(block.transportEvent, block);
    }

    return chain;
  }
}

// Singleton instance
let transportChainInstance: TransportChain | null = null;

export function getTransportChain(): TransportChain {
  if (!transportChainInstance) {
    transportChainInstance = new TransportChain();
  }
  return transportChainInstance;
}

export function setTransportChain(chain: TransportChain): void {
  transportChainInstance = chain;
}
