/**
 * PlantChainDB - Database-backed blockchain for plant lineage tracking
 * This implementation uses PostgreSQL via Prisma for persistence while
 * maintaining blockchain data integrity guarantees
 */

import { PlantBlock } from './PlantBlock';
import type { PlantData, PlantLineage, NearbyPlant, PlantNetwork } from './types';
import * as db from '../db';
import type { Plant, BlockchainBlock } from '../db/types';

/**
 * Convert database Plant model to PlantData format
 */
function plantToPlantData(plant: Plant & { owner?: { id: string; name: string; email: string } }): PlantData {
  return {
    id: plant.id,
    commonName: plant.commonName,
    scientificName: plant.scientificName || undefined,
    species: plant.species || undefined,
    genus: plant.genus || undefined,
    family: plant.family || undefined,
    parentPlantId: plant.parentPlantId || undefined,
    propagationType: plant.propagationType.toLowerCase() as PlantData['propagationType'],
    generation: plant.generation,
    plantedDate: plant.plantedDate.toISOString(),
    harvestedDate: plant.harvestedDate?.toISOString(),
    status: plant.status.toLowerCase() as PlantData['status'],
    location: {
      latitude: plant.latitude,
      longitude: plant.longitude,
      address: plant.address || undefined,
      city: plant.city || undefined,
      country: plant.country || undefined,
    },
    owner: plant.owner ? {
      id: plant.owner.id,
      name: plant.owner.name,
      email: plant.owner.email,
    } : {
      id: plant.ownerId,
      name: 'Unknown',
      email: 'unknown@localgreenchain.io',
    },
    childPlants: [], // Will be populated separately if needed
    environment: plant.environment as PlantData['environment'],
    growthMetrics: plant.growthMetrics as PlantData['growthMetrics'],
    notes: plant.notes || undefined,
    images: plant.images || undefined,
    plantsNetId: plant.plantsNetId || undefined,
    registeredAt: plant.registeredAt.toISOString(),
    updatedAt: plant.updatedAt.toISOString(),
  };
}

/**
 * PlantChainDB - Database-backed plant blockchain
 */
export class PlantChainDB {
  public difficulty: number;

  constructor(difficulty: number = 4) {
    this.difficulty = difficulty;
  }

  /**
   * Initialize the chain with genesis block if needed
   */
  async initialize(): Promise<void> {
    const latestBlock = await db.getLatestBlockchainBlock();
    if (!latestBlock) {
      await this.createGenesisBlock();
    }
  }

  /**
   * Create the genesis block
   */
  private async createGenesisBlock(): Promise<BlockchainBlock> {
    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');

    return db.createBlockchainBlock({
      index: 0,
      timestamp: new Date(),
      previousHash: '0',
      hash: block.hash,
      nonce: 0,
      blockType: 'genesis',
      content: { plant: genesisPlant },
    });
  }

  /**
   * Get the latest block
   */
  async getLatestBlock(): Promise<BlockchainBlock | null> {
    return db.getLatestBlockchainBlock();
  }

  /**
   * Register a new plant and add to blockchain
   */
  async registerPlant(plantData: {
    id?: string;
    commonName: string;
    scientificName?: string;
    species?: string;
    genus?: string;
    family?: string;
    plantedDate?: Date;
    status?: 'SPROUTED' | 'GROWING' | 'MATURE' | 'FLOWERING' | 'FRUITING' | 'DORMANT' | 'DECEASED';
    latitude: number;
    longitude: number;
    address?: string;
    city?: string;
    country?: string;
    ownerId: string;
    notes?: string;
    images?: string[];
    plantsNetId?: string;
  }): Promise<{ plant: Plant; block: BlockchainBlock }> {
    // Create plant in database
    const plant = await db.createPlant({
      ...plantData,
      plantedDate: plantData.plantedDate || new Date(),
      propagationType: 'ORIGINAL',
      generation: 0,
    });

    // Get latest block for previous hash
    const latestBlock = await this.getLatestBlock();
    const previousHash = latestBlock?.hash || '0';
    const nextIndex = (latestBlock?.index ?? -1) + 1;

    // Create PlantBlock for mining
    const plantDataForBlock = await this.getPlantData(plant.id);
    const newBlock = new PlantBlock(
      nextIndex,
      new Date().toISOString(),
      plantDataForBlock!,
      previousHash
    );
    newBlock.mineBlock(this.difficulty);

    // Store block in database
    const blockchainBlock = await db.createBlockchainBlock({
      index: nextIndex,
      timestamp: new Date(),
      previousHash,
      hash: newBlock.hash,
      nonce: newBlock.nonce,
      blockType: 'plant',
      plantId: plant.id,
      content: { plant: plantDataForBlock, action: 'REGISTER' },
    });

    // Update plant with blockchain reference
    await db.updatePlantBlockchain(plant.id, nextIndex, newBlock.hash);

    return { plant, block: blockchainBlock };
  }

  /**
   * Clone a plant and add to blockchain
   */
  async clonePlant(
    parentPlantId: string,
    ownerId: string,
    propagationType: 'SEED' | 'CLONE' | 'CUTTING' | 'DIVISION' | 'GRAFTING',
    overrides?: {
      latitude?: number;
      longitude?: number;
      address?: string;
      city?: string;
      country?: string;
      notes?: string;
    }
  ): Promise<{ plant: Plant; block: BlockchainBlock }> {
    // Create cloned plant in database
    const plant = await db.clonePlant(
      parentPlantId,
      ownerId,
      propagationType,
      overrides
    );

    // Get latest block for previous hash
    const latestBlock = await this.getLatestBlock();
    const previousHash = latestBlock?.hash || '0';
    const nextIndex = (latestBlock?.index ?? -1) + 1;

    // Create PlantBlock for mining
    const plantDataForBlock = await this.getPlantData(plant.id);
    const newBlock = new PlantBlock(
      nextIndex,
      new Date().toISOString(),
      plantDataForBlock!,
      previousHash
    );
    newBlock.mineBlock(this.difficulty);

    // Store block in database
    const blockchainBlock = await db.createBlockchainBlock({
      index: nextIndex,
      timestamp: new Date(),
      previousHash,
      hash: newBlock.hash,
      nonce: newBlock.nonce,
      blockType: 'plant',
      plantId: plant.id,
      content: {
        plant: plantDataForBlock,
        action: 'CLONE',
        parentPlantId,
      },
    });

    // Update plant with blockchain reference
    await db.updatePlantBlockchain(plant.id, nextIndex, newBlock.hash);

    return { plant, block: blockchainBlock };
  }

  /**
   * Update plant status and add to blockchain
   */
  async updatePlantStatus(
    plantId: string,
    status: 'SPROUTED' | 'GROWING' | 'MATURE' | 'FLOWERING' | 'FRUITING' | 'DORMANT' | 'DECEASED',
    harvestedDate?: Date
  ): Promise<{ plant: Plant; block: BlockchainBlock }> {
    // Update plant in database
    const plant = await db.updatePlantStatus(plantId, status, harvestedDate);

    // Get latest block for previous hash
    const latestBlock = await this.getLatestBlock();
    const previousHash = latestBlock?.hash || '0';
    const nextIndex = (latestBlock?.index ?? -1) + 1;

    // Create PlantBlock for mining
    const plantDataForBlock = await this.getPlantData(plant.id);
    const newBlock = new PlantBlock(
      nextIndex,
      new Date().toISOString(),
      plantDataForBlock!,
      previousHash
    );
    newBlock.mineBlock(this.difficulty);

    // Store block in database
    const blockchainBlock = await db.createBlockchainBlock({
      index: nextIndex,
      timestamp: new Date(),
      previousHash,
      hash: newBlock.hash,
      nonce: newBlock.nonce,
      blockType: 'plant',
      plantId: plant.id,
      content: {
        plant: plantDataForBlock,
        action: 'UPDATE_STATUS',
        newStatus: status,
      },
    });

    // Update plant with blockchain reference
    await db.updatePlantBlockchain(plant.id, nextIndex, newBlock.hash);

    return { plant, block: blockchainBlock };
  }

  /**
   * Get plant data in PlantData format
   */
  async getPlantData(plantId: string): Promise<PlantData | null> {
    const plant = await db.getPlantWithOwner(plantId);
    if (!plant) return null;

    const plantData = plantToPlantData(plant as Plant & { owner: { id: string; name: string; email: string } });

    // Get child plant IDs
    const lineage = await db.getPlantLineage(plantId);
    plantData.childPlants = lineage.descendants.map(d => d.id);

    return plantData;
  }

  /**
   * Get a plant by ID
   */
  async getPlant(plantId: string): Promise<Plant | null> {
    return db.getPlantById(plantId);
  }

  /**
   * Get plant with owner
   */
  async getPlantWithOwner(plantId: string) {
    return db.getPlantWithOwner(plantId);
  }

  /**
   * Get complete lineage for a plant
   */
  async getPlantLineage(plantId: string): Promise<PlantLineage | null> {
    const result = await db.getPlantLineage(plantId);
    if (!result.plant) return null;

    const plant = result.plant as Plant & { owner: { id: string; name: string; email: string } };

    return {
      plantId,
      ancestors: result.ancestors.map(p => plantToPlantData(p as Plant & { owner: { id: string; name: string; email: string } })),
      descendants: result.descendants.map(p => plantToPlantData(p as Plant & { owner: { id: string; name: string; email: string } })),
      siblings: result.siblings.map(p => plantToPlantData(p as Plant & { owner: { id: string; name: string; email: string } })),
      generation: plant.generation,
    };
  }

  /**
   * Find plants near a location
   */
  async findNearbyPlants(
    latitude: number,
    longitude: number,
    radiusKm: number = 50,
    excludeOwnerId?: string
  ): Promise<NearbyPlant[]> {
    const plants = await db.getNearbyPlants(
      { latitude, longitude, radiusKm },
      excludeOwnerId
    );

    return plants.map(p => ({
      plant: plantToPlantData(p as Plant & { owner: { id: string; name: string; email: string } }),
      distance: p.distance,
      owner: {
        id: p.ownerId,
        name: 'Unknown',
        email: 'unknown@localgreenchain.io',
      },
    }));
  }

  /**
   * Get network statistics
   */
  async getNetworkStats(): Promise<PlantNetwork> {
    return db.getPlantNetworkStats();
  }

  /**
   * Validate the blockchain integrity
   */
  async isChainValid(): Promise<boolean> {
    const result = await db.verifyBlockchainIntegrity();
    return result.isValid;
  }

  /**
   * Get blockchain statistics
   */
  async getBlockchainStats() {
    return db.getBlockchainStats();
  }

  /**
   * Search plants
   */
  async searchPlants(query: string, options?: { page?: number; limit?: number }) {
    return db.searchPlants(query, options);
  }

  /**
   * Get plants by owner
   */
  async getPlantsByOwner(ownerId: string) {
    return db.getPlantsByOwner(ownerId);
  }
}

// Singleton instance
let plantChainInstance: PlantChainDB | null = null;

/**
 * Get the singleton PlantChainDB instance
 */
export async function getPlantChain(): Promise<PlantChainDB> {
  if (!plantChainInstance) {
    plantChainInstance = new PlantChainDB();
    await plantChainInstance.initialize();
  }
  return plantChainInstance;
}

export default PlantChainDB;
