/**
 * Plant Database Service
 * CRUD operations for plants and lineage tracking
 */

import prisma from './prisma';
import type { Plant, PlantStatus, PropagationType, Prisma } from '@prisma/client';
import type { PaginationOptions, PaginatedResult, LocationFilter, PlantWithLineage } from './types';
import { createPaginatedResult, calculateDistanceKm } from './types';

// Create a new plant
export async function createPlant(data: {
  commonName: string;
  scientificName?: string;
  species?: string;
  genus?: string;
  family?: string;
  parentPlantId?: string;
  propagationType?: PropagationType;
  generation?: number;
  plantedDate: Date;
  status?: PlantStatus;
  latitude: number;
  longitude: number;
  address?: string;
  city?: string;
  country?: string;
  ownerId: string;
  environment?: Record<string, unknown>;
  growthMetrics?: Record<string, unknown>;
  notes?: string;
  images?: string[];
  plantsNetId?: string;
}): Promise<Plant> {
  // Determine generation based on parent
  let generation = data.generation || 0;
  if (data.parentPlantId && generation === 0) {
    const parent = await prisma.plant.findUnique({
      where: { id: data.parentPlantId },
      select: { generation: true },
    });
    if (parent) {
      generation = parent.generation + 1;
    }
  }

  return prisma.plant.create({
    data: {
      ...data,
      generation,
      propagationType: data.propagationType || 'ORIGINAL',
      status: data.status || 'SPROUTED',
    },
  });
}

// Get plant by ID
export async function getPlantById(id: string): Promise<Plant | null> {
  return prisma.plant.findUnique({
    where: { id },
  });
}

// Get plant with owner
export async function getPlantWithOwner(id: string) {
  return prisma.plant.findUnique({
    where: { id },
    include: { owner: true },
  });
}

// Get plant with full lineage
export async function getPlantWithLineage(id: string): Promise<PlantWithLineage | null> {
  return prisma.plant.findUnique({
    where: { id },
    include: {
      owner: true,
      parentPlant: true,
      childPlants: true,
    },
  });
}

// Update plant
export async function updatePlant(
  id: string,
  data: Prisma.PlantUpdateInput
): Promise<Plant> {
  return prisma.plant.update({
    where: { id },
    data,
  });
}

// Update plant status
export async function updatePlantStatus(
  id: string,
  status: PlantStatus,
  harvestedDate?: Date
): Promise<Plant> {
  return prisma.plant.update({
    where: { id },
    data: {
      status,
      ...(harvestedDate && { harvestedDate }),
    },
  });
}

// Delete plant
export async function deletePlant(id: string): Promise<Plant> {
  return prisma.plant.delete({
    where: { id },
  });
}

// Get plants with pagination
export async function getPlants(
  options: PaginationOptions = {},
  filters?: {
    ownerId?: string;
    status?: PlantStatus;
    commonName?: string;
    species?: string;
  }
): Promise<PaginatedResult<Plant>> {
  const page = options.page || 1;
  const limit = options.limit || 20;
  const skip = (page - 1) * limit;

  const where: Prisma.PlantWhereInput = {};
  if (filters?.ownerId) where.ownerId = filters.ownerId;
  if (filters?.status) where.status = filters.status;
  if (filters?.commonName) where.commonName = { contains: filters.commonName, mode: 'insensitive' };
  if (filters?.species) where.species = { contains: filters.species, mode: 'insensitive' };

  const [plants, total] = await Promise.all([
    prisma.plant.findMany({
      where,
      skip,
      take: limit,
      orderBy: { registeredAt: 'desc' },
      include: { owner: true },
    }),
    prisma.plant.count({ where }),
  ]);

  return createPaginatedResult(plants, total, page, limit);
}

// Get plants by owner
export async function getPlantsByOwner(ownerId: string): Promise<Plant[]> {
  return prisma.plant.findMany({
    where: { ownerId },
    orderBy: { registeredAt: 'desc' },
  });
}

// Get plants near location
export async function getNearbyPlants(
  location: LocationFilter,
  excludeOwnerId?: string
): Promise<Array<Plant & { distance: number }>> {
  // Get all plants and filter by distance
  // Note: For production, consider using PostGIS for efficient geo queries
  const plants = await prisma.plant.findMany({
    where: excludeOwnerId ? { ownerId: { not: excludeOwnerId } } : undefined,
    include: { owner: true },
  });

  const plantsWithDistance = plants
    .map(plant => ({
      ...plant,
      distance: calculateDistanceKm(
        location.latitude,
        location.longitude,
        plant.latitude,
        plant.longitude
      ),
    }))
    .filter(plant => plant.distance <= location.radiusKm)
    .sort((a, b) => a.distance - b.distance);

  return plantsWithDistance;
}

// Search plants
export async function searchPlants(
  query: string,
  options: PaginationOptions = {}
): Promise<PaginatedResult<Plant>> {
  const page = options.page || 1;
  const limit = options.limit || 20;
  const skip = (page - 1) * limit;

  const where: Prisma.PlantWhereInput = {
    OR: [
      { commonName: { contains: query, mode: 'insensitive' } },
      { scientificName: { contains: query, mode: 'insensitive' } },
      { species: { contains: query, mode: 'insensitive' } },
      { genus: { contains: query, mode: 'insensitive' } },
      { family: { contains: query, mode: 'insensitive' } },
    ],
  };

  const [plants, total] = await Promise.all([
    prisma.plant.findMany({
      where,
      skip,
      take: limit,
      orderBy: { registeredAt: 'desc' },
      include: { owner: true },
    }),
    prisma.plant.count({ where }),
  ]);

  return createPaginatedResult(plants, total, page, limit);
}

// Get full lineage (ancestors and descendants)
export async function getPlantLineage(id: string): Promise<{
  plant: Plant | null;
  ancestors: Plant[];
  descendants: Plant[];
  siblings: Plant[];
}> {
  const plant = await prisma.plant.findUnique({
    where: { id },
    include: {
      parentPlant: true,
      childPlants: true,
    },
  });

  if (!plant) {
    return { plant: null, ancestors: [], descendants: [], siblings: [] };
  }

  // Get ancestors recursively
  const ancestors: Plant[] = [];
  let currentParentId = plant.parentPlantId;
  while (currentParentId) {
    const parent = await prisma.plant.findUnique({
      where: { id: currentParentId },
    });
    if (parent) {
      ancestors.push(parent);
      currentParentId = parent.parentPlantId;
    } else {
      break;
    }
  }

  // Get all descendants recursively
  const descendants = await getDescendants(id);

  // Get siblings (other plants from the same parent)
  let siblings: Plant[] = [];
  if (plant.parentPlantId) {
    siblings = await prisma.plant.findMany({
      where: {
        parentPlantId: plant.parentPlantId,
        id: { not: id },
      },
    });
  }

  return {
    plant,
    ancestors,
    descendants,
    siblings,
  };
}

// Helper to get all descendants
async function getDescendants(plantId: string): Promise<Plant[]> {
  const children = await prisma.plant.findMany({
    where: { parentPlantId: plantId },
  });

  const allDescendants: Plant[] = [...children];
  for (const child of children) {
    const grandchildren = await getDescendants(child.id);
    allDescendants.push(...grandchildren);
  }

  return allDescendants;
}

// Clone a plant
export async function clonePlant(
  parentId: string,
  ownerId: string,
  propagationType: PropagationType = 'CLONE',
  overrides?: Partial<{
    latitude: number;
    longitude: number;
    address: string;
    city: string;
    country: string;
    notes: string;
  }>
): Promise<Plant> {
  const parent = await prisma.plant.findUnique({
    where: { id: parentId },
  });

  if (!parent) {
    throw new Error('Parent plant not found');
  }

  return prisma.plant.create({
    data: {
      commonName: parent.commonName,
      scientificName: parent.scientificName,
      species: parent.species,
      genus: parent.genus,
      family: parent.family,
      parentPlantId: parentId,
      propagationType,
      generation: parent.generation + 1,
      plantedDate: new Date(),
      status: 'SPROUTED',
      latitude: overrides?.latitude || parent.latitude,
      longitude: overrides?.longitude || parent.longitude,
      address: overrides?.address,
      city: overrides?.city || parent.city,
      country: overrides?.country || parent.country,
      ownerId,
      notes: overrides?.notes,
      plantsNetId: parent.plantsNetId,
    },
  });
}

// Get plant network statistics
export async function getPlantNetworkStats() {
  const [totalPlants, speciesDistribution, countryDistribution, ownerCount] = await Promise.all([
    prisma.plant.count(),
    prisma.plant.groupBy({
      by: ['species'],
      _count: { species: true },
      where: { species: { not: null } },
    }),
    prisma.plant.groupBy({
      by: ['country'],
      _count: { country: true },
      where: { country: { not: null } },
    }),
    prisma.user.count({
      where: { ownedPlants: { some: {} } },
    }),
  ]);

  return {
    totalPlants,
    totalOwners: ownerCount,
    species: Object.fromEntries(
      speciesDistribution.map(s => [s.species || 'unknown', s._count.species])
    ),
    globalDistribution: Object.fromEntries(
      countryDistribution.map(c => [c.country || 'unknown', c._count.country])
    ),
  };
}

// Store blockchain data for a plant
export async function updatePlantBlockchain(
  id: string,
  blockIndex: number,
  blockHash: string
): Promise<Plant> {
  return prisma.plant.update({
    where: { id },
    data: { blockIndex, blockHash },
  });
}
