/**
 * VerticalFarmController Tests
 * Tests for the vertical farm management system
 */

import {
  VerticalFarmController,
  getVerticalFarmController,
} from '../../../lib/vertical-farming/controller';
import {
  VerticalFarm,
  GrowingZone,
  CropBatch,
  GrowingRecipe,
  ZoneEnvironmentReadings,
} from '../../../lib/vertical-farming/types';

describe('VerticalFarmController', () => {
  let controller: VerticalFarmController;

  beforeEach(() => {
    controller = new VerticalFarmController();
  });

  describe('Initialization', () => {
    it('should initialize with default recipes', () => {
      const recipes = controller.getRecipes();
      expect(recipes.length).toBeGreaterThan(0);
    });

    it('should have lettuce recipe', () => {
      const recipes = controller.getRecipes();
      const lettuceRecipe = recipes.find(r => r.cropType === 'lettuce');
      expect(lettuceRecipe).toBeDefined();
    });

    it('should have basil recipe', () => {
      const recipes = controller.getRecipes();
      const basilRecipe = recipes.find(r => r.cropType === 'basil');
      expect(basilRecipe).toBeDefined();
    });

    it('should have microgreens recipe', () => {
      const recipes = controller.getRecipes();
      const microgreensRecipe = recipes.find(r => r.cropType === 'microgreens');
      expect(microgreensRecipe).toBeDefined();
    });
  });

  describe('Farm Registration', () => {
    it('should register a vertical farm', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const retrieved = controller.getFarm('farm-001');
      expect(retrieved).toBeDefined();
      expect(retrieved?.name).toBe('Test Vertical Farm');
    });

    it('should track multiple farms', () => {
      controller.registerFarm(createVerticalFarm('farm-001'));
      controller.registerFarm(createVerticalFarm('farm-002'));

      expect(controller.getFarm('farm-001')).toBeDefined();
      expect(controller.getFarm('farm-002')).toBeDefined();
    });

    it('should return undefined for unknown farm', () => {
      expect(controller.getFarm('unknown-farm')).toBeUndefined();
    });
  });

  describe('Crop Batch Management', () => {
    it('should start crop batch with recipe', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const lettuceRecipe = recipes.find(r => r.cropType === 'lettuce')!;

      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        lettuceRecipe.id,
        'seed-batch-001',
        100
      );

      expect(batch.id).toBeDefined();
      expect(batch.farmId).toBe('farm-001');
      expect(batch.zoneId).toBe('zone-001');
      expect(batch.plantCount).toBe(100);
      expect(batch.status).toBe('germinating');
    });

    it('should generate plant IDs for batch', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const recipe = recipes[0];

      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipe.id,
        'seed-batch-001',
        50
      );

      expect(batch.plantIds.length).toBe(50);
      expect(batch.plantIds[0]).toContain('plant-');
    });

    it('should calculate expected yield', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const lettuceRecipe = recipes.find(r => r.cropType === 'lettuce')!;

      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        lettuceRecipe.id,
        'seed-batch-001',
        100
      );

      // Expected yield = expectedYieldGrams * plantCount / 1000
      const expectedYield = (lettuceRecipe.expectedYieldGrams * 100) / 1000;
      expect(batch.expectedYieldKg).toBe(expectedYield);
    });

    it('should update zone status when starting batch', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipes[0].id,
        'seed-batch-001',
        100
      );

      const updatedFarm = controller.getFarm('farm-001');
      const zone = updatedFarm?.zones.find(z => z.id === 'zone-001');

      expect(zone?.status).toBe('planted');
      expect(zone?.currentCrop).toBe(recipes[0].cropType);
      expect(zone?.plantIds.length).toBe(100);
    });

    it('should throw error for unknown farm', () => {
      expect(() => {
        controller.startCropBatch(
          'unknown-farm',
          'zone-001',
          'recipe-001',
          'seed-001',
          100
        );
      }).toThrow('Farm unknown-farm not found');
    });

    it('should throw error for unknown zone', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      expect(() => {
        controller.startCropBatch(
          'farm-001',
          'unknown-zone',
          'recipe-001',
          'seed-001',
          100
        );
      }).toThrow('Zone unknown-zone not found');
    });

    it('should throw error for unknown recipe', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      expect(() => {
        controller.startCropBatch(
          'farm-001',
          'zone-001',
          'unknown-recipe',
          'seed-001',
          100
        );
      }).toThrow('Recipe unknown-recipe not found');
    });
  });

  describe('Batch Progress', () => {
    it('should update batch progress', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipes[0].id,
        'seed-batch-001',
        100
      );

      const updated = controller.updateBatchProgress(batch.id);
      expect(updated.currentDay).toBeGreaterThanOrEqual(0);
    });

    it('should update current stage based on day', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipes[0].id,
        'seed-batch-001',
        100
      );

      // Initial stage should be first stage
      expect(batch.currentStage).toBe(recipes[0].stages[0].name);
    });

    it('should throw error for unknown batch', () => {
      expect(() => {
        controller.updateBatchProgress('unknown-batch');
      }).toThrow('Batch unknown-batch not found');
    });
  });

  describe('Harvest Completion', () => {
    it('should complete harvest and record yield', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipes[0].id,
        'seed-batch-001',
        100
      );

      const completed = controller.completeHarvest(batch.id, 15.5, 'A');

      expect(completed.status).toBe('completed');
      expect(completed.actualYieldKg).toBe(15.5);
      expect(completed.qualityGrade).toBe('A');
      expect(completed.actualHarvestDate).toBeDefined();
    });

    it('should update zone after harvest', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const batch = controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipes[0].id,
        'seed-batch-001',
        100
      );

      controller.completeHarvest(batch.id, 15.5, 'A');

      const updatedFarm = controller.getFarm('farm-001');
      const zone = updatedFarm?.zones.find(z => z.id === 'zone-001');

      expect(zone?.status).toBe('cleaning');
      expect(zone?.currentCrop).toBe('');
      expect(zone?.plantIds.length).toBe(0);
    });

    it('should throw error for unknown batch', () => {
      expect(() => {
        controller.completeHarvest('unknown-batch', 10, 'A');
      }).toThrow('Batch unknown-batch not found');
    });
  });

  describe('Recipe Management', () => {
    it('should add custom recipe', () => {
      const customRecipe: GrowingRecipe = {
        id: 'recipe-custom-spinach',
        name: 'Custom Spinach Recipe',
        cropType: 'spinach',
        version: '1.0',
        stages: [
          {
            name: 'Germination',
            daysStart: 0,
            daysEnd: 5,
            temperature: { day: 20, night: 18 },
            humidity: { day: 80, night: 85 },
            co2Ppm: 800,
            lightHours: 16,
            lightPpfd: 150,
            nutrientRecipeId: 'nutrient-seedling',
            targetEc: 0.8,
            targetPh: 6.0,
            actions: [],
          },
        ],
        expectedDays: 40,
        expectedYieldGrams: 100,
        expectedYieldPerSqm: 3000,
        requirements: {
          positions: 1,
          zoneType: 'NFT',
          minimumPpfd: 200,
          idealTemperatureC: 18,
        },
        source: 'internal',
        timesUsed: 0,
      };

      controller.addRecipe(customRecipe);

      const recipes = controller.getRecipes();
      const added = recipes.find(r => r.id === 'recipe-custom-spinach');
      expect(added).toBeDefined();
      expect(added?.name).toBe('Custom Spinach Recipe');
    });

    it('should track recipe usage', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      const recipe = recipes[0];
      const initialUsage = recipe.timesUsed;

      controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipe.id,
        'seed-batch-001',
        100
      );

      expect(recipe.timesUsed).toBe(initialUsage + 1);
    });
  });

  describe('Singleton', () => {
    it('should return same instance from getVerticalFarmController', () => {
      const controller1 = getVerticalFarmController();
      const controller2 = getVerticalFarmController();
      expect(controller1).toBe(controller2);
    });
  });

  describe('Serialization', () => {
    it('should export to JSON', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const json = controller.toJSON();
      expect(json).toHaveProperty('farms');
      expect(json).toHaveProperty('recipes');
      expect(json).toHaveProperty('batches');
    });

    it('should import from JSON', () => {
      const farm = createVerticalFarm('farm-001');
      controller.registerFarm(farm);

      const recipes = controller.getRecipes();
      controller.startCropBatch(
        'farm-001',
        'zone-001',
        recipes[0].id,
        'seed-batch-001',
        100
      );

      const json = controller.toJSON();
      const restored = VerticalFarmController.fromJSON(json);

      expect(restored.getFarm('farm-001')).toBeDefined();
      expect(restored.getRecipes().length).toBeGreaterThan(0);
    });
  });
});

// Helper functions
function createVerticalFarm(id: string): VerticalFarm {
  return {
    id,
    name: 'Test Vertical Farm',
    ownerId: 'owner-001',
    location: {
      latitude: 40.7128,
      longitude: -74.006,
      address: '123 Farm Street',
      city: 'New York',
      country: 'USA',
      timezone: 'America/New_York',
    },
    specs: {
      totalAreaSqm: 500,
      growingAreaSqm: 400,
      numberOfLevels: 4,
      ceilingHeightM: 3,
      totalGrowingPositions: 4000,
      currentActivePlants: 0,
      powerCapacityKw: 100,
      waterStorageL: 5000,
      backupPowerHours: 24,
      certifications: ['organic', 'gap'],
      buildingType: 'warehouse',
      insulation: 'high_efficiency',
    },
    zones: [
      createGrowingZone('zone-001', 'Zone A', 1),
      createGrowingZone('zone-002', 'Zone B', 1),
    ],
    environmentalControl: {
      hvacUnits: [],
      co2Injection: { type: 'tank', capacityKg: 50, currentLevelKg: 40, injectionRateKgPerHour: 2, status: 'maintaining' },
      humidification: { type: 'ultrasonic', capacityLPerHour: 10, status: 'running', currentOutput: 5 },
      airCirculation: { fans: [] },
      controlMode: 'adaptive',
    },
    irrigationSystem: {
      type: 'recirculating',
      freshWaterTankL: 2000,
      freshWaterLevelL: 1800,
      nutrientTankL: 500,
      nutrientLevelL: 450,
      wasteTankL: 200,
      wasteLevelL: 50,
      waterTreatment: { ro: true, uv: true, ozone: false, filtration: '10 micron' },
      pumps: [],
      irrigationSchedule: [],
    },
    lightingSystem: {
      type: 'LED',
      fixtures: [],
      lightSchedules: [],
      totalWattage: 5000,
      currentWattage: 3000,
      efficacyUmolJ: 2.5,
    },
    nutrientSystem: {
      mixingMethod: 'fully_auto',
      stockSolutions: [],
      dosingPumps: [],
      currentRecipe: {
        id: 'default',
        name: 'Default',
        cropType: 'general',
        growthStage: 'vegetative',
        targetEc: 1.5,
        targetPh: 6.0,
        ratios: { n: 200, p: 50, k: 200, ca: 200, mg: 50, s: 100, fe: 5, mn: 0.5, zn: 0.3, cu: 0.1, b: 0.5, mo: 0.05 },
        dosingRatiosMlPerL: [],
      },
      monitoring: {
        ec: 1.5,
        ph: 6.0,
        lastCalibration: new Date().toISOString(),
        calibrationDue: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
      },
    },
    automationLevel: 'semi_automated',
    automationSystems: [],
    status: 'operational',
    operationalSince: '2024-01-01',
    lastMaintenanceDate: new Date().toISOString(),
    currentCapacityUtilization: 75,
    averageYieldEfficiency: 85,
    energyEfficiencyScore: 80,
  };
}

function createGrowingZone(id: string, name: string, level: number): GrowingZone {
  return {
    id,
    name,
    level,
    areaSqm: 50,
    lengthM: 10,
    widthM: 5,
    growingMethod: 'NFT',
    plantPositions: 500,
    currentCrop: '',
    plantIds: [],
    plantingDate: '',
    expectedHarvestDate: '',
    environmentTargets: {
      temperatureC: { min: 18, max: 24, target: 21 },
      humidityPercent: { min: 60, max: 80, target: 70 },
      co2Ppm: { min: 800, max: 1200, target: 1000 },
      lightPpfd: { min: 200, max: 400, target: 300 },
      lightHours: 16,
      nutrientEc: { min: 1.2, max: 1.8, target: 1.5 },
      nutrientPh: { min: 5.8, max: 6.2, target: 6.0 },
      waterTempC: { min: 18, max: 22, target: 20 },
    },
    currentEnvironment: {
      timestamp: new Date().toISOString(),
      temperatureC: 21,
      humidityPercent: 70,
      co2Ppm: 1000,
      vpd: 1.0,
      ppfd: 300,
      dli: 17,
      waterTempC: 20,
      ec: 1.5,
      ph: 6.0,
      dissolvedOxygenPpm: 8,
      airflowMs: 0.5,
      alerts: [],
    },
    status: 'empty',
  };
}
