import { PlantBlock } from './PlantBlock';
import { PlantData, PlantLineage, NearbyPlant, PlantNetwork } from './types';

/**
 * PlantChain - The main blockchain for tracking plant lineage and ownership
 * This blockchain records every plant, its clones, seeds, and ownership transfers
 */
export class PlantChain {
  public chain: PlantBlock[];
  public difficulty: number;
  private plantIndex: Map<string, PlantBlock>; // Quick lookup by plant ID

  constructor(difficulty: number = 4) {
    this.chain = [this.createGenesisBlock()];
    this.difficulty = difficulty;
    this.plantIndex = new Map();
  }

  /**
   * Create the first block in the chain
   */
  private createGenesisBlock(): PlantBlock {
    const genesisPlant: PlantData = {
      id: 'genesis-plant-0',
      commonName: 'Genesis Plant',
      scientificName: 'Blockchain primordialis',
      propagationType: 'original',
      generation: 0,
      plantedDate: new Date().toISOString(),
      status: 'mature',
      location: {
        latitude: 0,
        longitude: 0,
        address: 'The Beginning',
      },
      owner: {
        id: 'system',
        name: 'LocalGreenChain',
        email: 'system@localgreenchain.org',
      },
      childPlants: [],
      registeredAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };

    const block = new PlantBlock(0, new Date().toISOString(), genesisPlant, '0');
    this.plantIndex.set(genesisPlant.id, block);
    return block;
  }

  /**
   * Get the latest block in the chain
   */
  getLatestBlock(): PlantBlock {
    return this.chain[this.chain.length - 1];
  }

  /**
   * Register a new original plant (not a clone or seed)
   */
  registerPlant(plant: PlantData): PlantBlock {
    // Ensure plant has required fields
    if (!plant.id || !plant.commonName || !plant.owner) {
      throw new Error('Plant must have id, commonName, and owner');
    }

    // Check if plant already exists
    if (this.plantIndex.has(plant.id)) {
      throw new Error(`Plant with ID ${plant.id} already exists`);
    }

    // Set defaults
    plant.generation = 0;
    plant.propagationType = plant.propagationType || 'original';
    plant.childPlants = [];
    plant.registeredAt = new Date().toISOString();
    plant.updatedAt = new Date().toISOString();

    const newBlock = new PlantBlock(
      this.chain.length,
      new Date().toISOString(),
      plant,
      this.getLatestBlock().hash
    );

    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
    this.plantIndex.set(plant.id, newBlock);

    return newBlock;
  }

  /**
   * Register a clone or seed from an existing plant
   */
  clonePlant(
    parentPlantId: string,
    newPlant: Partial<PlantData>,
    propagationType: 'seed' | 'clone' | 'cutting' | 'division' | 'grafting'
  ): PlantBlock {
    // Find parent plant
    const parentBlock = this.plantIndex.get(parentPlantId);
    if (!parentBlock) {
      throw new Error(`Parent plant ${parentPlantId} not found`);
    }

    const parentPlant = parentBlock.plant;

    // Create new plant with inherited properties
    const clonedPlant: PlantData = {
      id: newPlant.id || `${parentPlantId}-${propagationType}-${Date.now()}`,
      commonName: newPlant.commonName || parentPlant.commonName,
      scientificName: newPlant.scientificName || parentPlant.scientificName,
      species: newPlant.species || parentPlant.species,
      genus: newPlant.genus || parentPlant.genus,
      family: newPlant.family || parentPlant.family,

      // Lineage
      parentPlantId: parentPlantId,
      propagationType: propagationType,
      generation: parentPlant.generation + 1,

      // Required fields from newPlant
      plantedDate: newPlant.plantedDate || new Date().toISOString(),
      status: newPlant.status || 'sprouted',
      location: newPlant.location!,
      owner: newPlant.owner!,

      // Initialize child tracking
      childPlants: [],

      // Optional fields
      notes: newPlant.notes,
      images: newPlant.images,
      plantsNetId: newPlant.plantsNetId,

      registeredAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };

    // Validate required fields
    if (!clonedPlant.location || !clonedPlant.owner) {
      throw new Error('Cloned plant must have location and owner');
    }

    // Add to blockchain
    const newBlock = new PlantBlock(
      this.chain.length,
      new Date().toISOString(),
      clonedPlant,
      this.getLatestBlock().hash
    );

    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
    this.plantIndex.set(clonedPlant.id, newBlock);

    // Update parent plant's child list
    parentPlant.childPlants.push(clonedPlant.id);
    parentPlant.updatedAt = new Date().toISOString();

    return newBlock;
  }

  /**
   * Update plant status (growing, flowering, etc.)
   */
  updatePlantStatus(
    plantId: string,
    updates: Partial<PlantData>
  ): PlantBlock {
    const existingBlock = this.plantIndex.get(plantId);
    if (!existingBlock) {
      throw new Error(`Plant ${plantId} not found`);
    }

    const updatedPlant: PlantData = {
      ...existingBlock.plant,
      ...updates,
      id: existingBlock.plant.id, // Cannot change ID
      parentPlantId: existingBlock.plant.parentPlantId, // Cannot change lineage
      childPlants: existingBlock.plant.childPlants, // Cannot change children
      generation: existingBlock.plant.generation, // Cannot change generation
      registeredAt: existingBlock.plant.registeredAt, // Cannot change registration
      updatedAt: new Date().toISOString(),
    };

    const newBlock = new PlantBlock(
      this.chain.length,
      new Date().toISOString(),
      updatedPlant,
      this.getLatestBlock().hash
    );

    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
    this.plantIndex.set(plantId, newBlock); // Update index to latest block

    return newBlock;
  }

  /**
   * Get a plant by ID (returns latest version)
   */
  getPlant(plantId: string): PlantData | null {
    const block = this.plantIndex.get(plantId);
    return block ? block.plant : null;
  }

  /**
   * Get complete lineage for a plant
   */
  getPlantLineage(plantId: string): PlantLineage | null {
    const plant = this.getPlant(plantId);
    if (!plant) return null;

    // Get all ancestors
    const ancestors: PlantData[] = [];
    let currentPlant = plant;
    while (currentPlant.parentPlantId) {
      const parent = this.getPlant(currentPlant.parentPlantId);
      if (!parent) break;
      ancestors.push(parent);
      currentPlant = parent;
    }

    // Get all descendants recursively
    const descendants: PlantData[] = [];
    const getDescendants = (p: PlantData) => {
      for (const childId of p.childPlants) {
        const child = this.getPlant(childId);
        if (child) {
          descendants.push(child);
          getDescendants(child);
        }
      }
    };
    getDescendants(plant);

    // Get siblings (other plants from same parent)
    const siblings: PlantData[] = [];
    if (plant.parentPlantId) {
      const parent = this.getPlant(plant.parentPlantId);
      if (parent) {
        for (const siblingId of parent.childPlants) {
          if (siblingId !== plantId) {
            const sibling = this.getPlant(siblingId);
            if (sibling) siblings.push(sibling);
          }
        }
      }
    }

    return {
      plantId,
      ancestors,
      descendants,
      siblings,
      generation: plant.generation,
    };
  }

  /**
   * Find plants near a location (within radius in km)
   */
  findNearbyPlants(
    latitude: number,
    longitude: number,
    radiusKm: number = 50
  ): NearbyPlant[] {
    const nearbyPlants: NearbyPlant[] = [];

    // Get all unique plants (latest version of each)
    const allPlants = Array.from(this.plantIndex.values()).map(block => block.plant);

    for (const plant of allPlants) {
      const distance = this.calculateDistance(
        latitude,
        longitude,
        plant.location.latitude,
        plant.location.longitude
      );

      if (distance <= radiusKm) {
        nearbyPlants.push({
          plant,
          distance,
          owner: plant.owner,
        });
      }
    }

    // Sort by distance
    return nearbyPlants.sort((a, b) => a.distance - b.distance);
  }

  /**
   * Calculate distance between two coordinates using Haversine formula
   */
  private calculateDistance(
    lat1: number,
    lon1: number,
    lat2: number,
    lon2: number
  ): number {
    const R = 6371; // Earth's radius in km
    const dLat = this.toRadians(lat2 - lat1);
    const dLon = this.toRadians(lon2 - lon1);

    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(this.toRadians(lat1)) *
        Math.cos(this.toRadians(lat2)) *
        Math.sin(dLon / 2) *
        Math.sin(dLon / 2);

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

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

  /**
   * Get network statistics
   */
  getNetworkStats(): PlantNetwork {
    const allPlants = Array.from(this.plantIndex.values()).map(block => block.plant);
    const owners = new Set<string>();
    const species: { [key: string]: number } = {};
    const countries: { [key: string]: number } = {};

    for (const plant of allPlants) {
      owners.add(plant.owner.id);

      if (plant.scientificName) {
        species[plant.scientificName] = (species[plant.scientificName] || 0) + 1;
      }

      if (plant.location.country) {
        countries[plant.location.country] = (countries[plant.location.country] || 0) + 1;
      }
    }

    return {
      totalPlants: allPlants.length,
      totalOwners: owners.size,
      species,
      globalDistribution: countries,
    };
  }

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

      if (!currentBlock.isValid(previousBlock)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Export chain to JSON
   */
  toJSON(): any {
    return {
      difficulty: this.difficulty,
      chain: this.chain.map(block => block.toJSON()),
    };
  }

  /**
   * Import chain from JSON
   */
  static fromJSON(data: any): PlantChain {
    const plantChain = new PlantChain(data.difficulty);
    plantChain.chain = data.chain.map((blockData: any) =>
      PlantBlock.fromJSON(blockData)
    );

    // Rebuild index
    plantChain.plantIndex.clear();
    for (const block of plantChain.chain) {
      plantChain.plantIndex.set(block.plant.id, block);
    }

    return plantChain;
  }
}
