// Capability Registry Service
// Manages the registry of all capabilities with CRUD operations, dependency validation, and version management

import { v4 as uuidv4 } from 'uuid';
import type { Prisma } from '@prisma/client';
import prisma from '@/shared/database/prisma';
import { DbisError, ErrorCode } from '@/shared/types';
import { logger } from '@/infrastructure/monitoring/logger';
import {
  Capability,
  CapabilityBinding,
  CapabilityDependency,
  CreateCapabilityRequest,
  UpdateCapabilityRequest,
  CapabilityState,
} from '@/shared/solacenet/types';

export class CapabilityRegistryService {
  /**
   * Get all capabilities
   */
  async getAllCapabilities(): Promise<Capability[]> {
    const capabilities = await prisma.solacenet_capability.findMany({
      where: {
        status: {
          not: 'archived',
        },
      },
      orderBy: {
        name: 'asc',
      },
    });

    return capabilities.map(this.mapToCapability);
  }

  /**
   * Get capability by ID
   */
  async getCapabilityById(capabilityId: string): Promise<Capability | null> {
    const capability = await prisma.solacenet_capability.findUnique({
      where: { capabilityId },
    });

    if (!capability) {
      return null;
    }

    return this.mapToCapability(capability);
  }

  /**
   * Get capability dependencies
   */
  async getCapabilityDependencies(capabilityId: string): Promise<CapabilityDependency[]> {
    const dependencies = await prisma.solacenet_capability_dependency.findMany({
      where: {
        dependentId: capabilityId,
      },
      include: {
        dependency: true,
      },
    });

    return dependencies.map((dep) => ({
      id: dep.id,
      dependentId: dep.dependentId,
      dependencyId: dep.dependencyId,
      dependencyType: dep.dependencyType as 'required' | 'optional' | 'recommended',
      createdAt: dep.createdAt,
      updatedAt: dep.updatedAt,
    }));
  }

  /**
   * Create a new capability
   */
  async createCapability(request: CreateCapabilityRequest): Promise<Capability> {
    // Check if capability ID already exists
    const existing = await prisma.solacenet_capability.findUnique({
      where: { capabilityId: request.capabilityId },
    });

    if (existing) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        `Capability with ID ${request.capabilityId} already exists`
      );
    }

    // Validate dependencies if provided
    if (request.dependencies && request.dependencies.length > 0) {
      const isValid = await this.validateDependencies(request.capabilityId, request.dependencies);
      if (!isValid) {
        throw new DbisError(
          ErrorCode.VALIDATION_ERROR,
          'One or more dependencies are invalid or would create circular dependencies'
        );
      }
    }

    const capability = await prisma.solacenet_capability.create({
      data: {
        id: uuidv4(),
        capabilityId: request.capabilityId,
        name: request.name,
        version: request.version || '1.0.0',
        description: request.description,
        ownerTeam: request.ownerTeam,
        dependencies: (request.dependencies || []) as Prisma.InputJsonValue,
        configSchema:
          request.configSchema != null
            ? (request.configSchema as Prisma.InputJsonValue)
            : undefined,
        defaultState: request.defaultState || CapabilityState.DISABLED,
        status: 'active',
        metadata:
          request.metadata != null ? (request.metadata as Prisma.InputJsonValue) : undefined,
      },
    });

    // Create dependency relationships
    if (request.dependencies && request.dependencies.length > 0) {
      await Promise.all(
        request.dependencies.map((depId) =>
          prisma.solacenet_capability_dependency.create({
            data: {
              id: uuidv4(),
              dependentId: request.capabilityId,
              dependencyId: depId,
              dependencyType: 'required',
            },
          })
        )
      );
    }

    logger.info(`Capability created: ${request.capabilityId}`);
    return this.mapToCapability(capability);
  }

  /**
   * Update a capability
   */
  async updateCapability(
    capabilityId: string,
    request: UpdateCapabilityRequest
  ): Promise<Capability> {
    const existing = await prisma.solacenet_capability.findUnique({
      where: { capabilityId },
    });

    if (!existing) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Capability ${capabilityId} not found`);
    }

    // Validate dependencies if being updated
    if (request.dependencies) {
      const isValid = await this.validateDependencies(capabilityId, request.dependencies);
      if (!isValid) {
        throw new DbisError(
          ErrorCode.VALIDATION_ERROR,
          'One or more dependencies are invalid or would create circular dependencies'
        );
      }

      // Update dependency relationships
      // First, delete existing dependencies
      await prisma.solacenet_capability_dependency.deleteMany({
        where: { dependentId: capabilityId },
      });

      // Then create new ones
      await Promise.all(
        request.dependencies.map((depId) =>
          prisma.solacenet_capability_dependency.create({
            data: {
              id: uuidv4(),
              dependentId: capabilityId,
              dependencyId: depId,
              dependencyType: 'required',
            },
          })
        )
      );
    }

    const updateData: Prisma.solacenet_capabilityUpdateInput = {};
    if (request.name !== undefined) updateData.name = request.name;
    if (request.version !== undefined) updateData.version = request.version;
    if (request.description !== undefined) updateData.description = request.description;
    if (request.ownerTeam !== undefined) updateData.ownerTeam = request.ownerTeam;
    if (request.dependencies !== undefined) {
      updateData.dependencies = request.dependencies as Prisma.InputJsonValue;
    }
    if (request.configSchema !== undefined) {
      updateData.configSchema =
        request.configSchema != null
          ? (request.configSchema as Prisma.InputJsonValue)
          : undefined;
    }
    if (request.defaultState !== undefined) updateData.defaultState = request.defaultState;
    if (request.status !== undefined) updateData.status = request.status;
    if (request.metadata !== undefined) {
      updateData.metadata =
        request.metadata != null ? (request.metadata as Prisma.InputJsonValue) : undefined;
    }

    const capability = await prisma.solacenet_capability.update({
      where: { capabilityId },
      data: updateData,
    });

    logger.info(`Capability updated: ${capabilityId}`);
    return this.mapToCapability(capability);
  }

  /**
   * Delete a capability (soft delete by archiving)
   */
  async deleteCapability(capabilityId: string): Promise<void> {
    const existing = await prisma.solacenet_capability.findUnique({
      where: { capabilityId },
    });

    if (!existing) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Capability ${capabilityId} not found`);
    }

    // Check if any other capabilities depend on this one
    const dependents = await prisma.solacenet_capability_dependency.findMany({
      where: { dependencyId: capabilityId },
    });

    if (dependents.length > 0) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        `Cannot delete capability ${capabilityId}: ${dependents.length} capability(ies) depend on it`
      );
    }

    await prisma.solacenet_capability.update({
      where: { capabilityId },
      data: { status: 'archived' },
    });

    logger.info(`Capability archived: ${capabilityId}`);
  }

  /**
   * Validate dependencies - check for circular dependencies and existence
   */
  async validateDependencies(capabilityId: string, dependencies: string[]): Promise<boolean> {
    // Check that all dependencies exist
    const existingDeps = await prisma.solacenet_capability.findMany({
      where: {
        capabilityId: {
          in: dependencies,
        },
        status: {
          not: 'archived',
        },
      },
    });

    if (existingDeps.length !== dependencies.length) {
      return false;
    }

    // Check for circular dependencies using DFS
    const visited = new Set<string>();
    const recursionStack = new Set<string>();

    const hasCycle = (node: string): boolean => {
      if (recursionStack.has(node)) {
        return true; // Circular dependency detected
      }
      if (visited.has(node)) {
        return false;
      }

      visited.add(node);
      recursionStack.add(node);

      // Get dependencies of this node
      const deps = dependencies.filter((dep) => dep === node);
      for (const dep of deps) {
        if (hasCycle(dep)) {
          return true;
        }
      }

      recursionStack.delete(node);
      return false;
    };

    // Check if adding these dependencies would create a cycle
    // We need to check if any dependency depends on capabilityId
    const wouldCreateCycle = await Promise.all(
      dependencies.map(async (depId) => {
        // Check if depId has capabilityId as a dependency (directly or indirectly)
        const depDeps = await prisma.solacenet_capability_dependency.findMany({
          where: { dependentId: depId },
        });

        // Check recursively
        const checkIndirect = async (checkId: string): Promise<boolean> => {
          if (checkId === capabilityId) {
            return true;
          }
          const indirectDeps = await prisma.solacenet_capability_dependency.findMany({
            where: { dependentId: checkId },
          });
          for (const indirectDep of indirectDeps) {
            if (await checkIndirect(indirectDep.dependencyId)) {
              return true;
            }
          }
          return false;
        };

        for (const depDep of depDeps) {
          if (await checkIndirect(depDep.dependencyId)) {
            return true;
          }
        }
        return false;
      })
    );

    return !wouldCreateCycle.some((cycle) => cycle);
  }

  /**
   * Get capability bindings
   */
  async getCapabilityBindings(capabilityId: string): Promise<CapabilityBinding[]> {
    const bindings = await prisma.solacenet_capability_binding.findMany({
      where: { capabilityId },
      include: {
        provider: true,
      },
    });

    return bindings.map((binding) => ({
      id: binding.id,
      capabilityId: binding.capabilityId,
      providerId: binding.providerId ?? undefined,
      region: binding.region,
      config: binding.config as Record<string, unknown> | undefined,
      secretsRef: binding.secretsRef ?? undefined,
      status: binding.status as 'active' | 'inactive',
      createdAt: binding.createdAt,
      updatedAt: binding.updatedAt,
    }));
  }

  /**
   * Create capability binding
   */
  async createCapabilityBinding(
    capabilityId: string,
    region: string,
    providerId?: string,
    config?: Record<string, unknown>
  ): Promise<CapabilityBinding> {
    // Check if capability exists
    const capability = await prisma.solacenet_capability.findUnique({
      where: { capabilityId },
    });

    if (!capability) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Capability ${capabilityId} not found`);
    }

    // Check if binding already exists
    const existing = await prisma.solacenet_capability_binding.findUnique({
      where: {
        capabilityId_region: {
          capabilityId,
          region,
        },
      },
    });

    if (existing) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        `Binding for capability ${capabilityId} in region ${region} already exists`
      );
    }

    // Validate provider if provided
    if (providerId) {
      const provider = await prisma.solacenet_provider_connector.findUnique({
        where: { connectorId: providerId },
      });

      if (!provider) {
        throw new DbisError(ErrorCode.NOT_FOUND, `Provider connector ${providerId} not found`);
      }
    }

    const binding = await prisma.solacenet_capability_binding.create({
      data: {
        id: uuidv4(),
        capabilityId,
        providerId: providerId || null,
        region,
        config: config != null ? (config as Prisma.InputJsonValue) : undefined,
        status: 'active',
      },
    });

    logger.info(`Capability binding created: ${capabilityId} in ${region}`);
    return {
      id: binding.id,
      capabilityId: binding.capabilityId,
      providerId: binding.providerId || undefined,
      region: binding.region,
      config: binding.config as Record<string, unknown> | undefined,
      secretsRef: binding.secretsRef || undefined,
      status: binding.status as 'active' | 'inactive',
      createdAt: binding.createdAt,
      updatedAt: binding.updatedAt,
    };
  }

  /**
   * Map Prisma model to Capability type
   */
  private mapToCapability(capability: {
    id: string;
    capabilityId: string;
    name: string;
    version: string;
    description: string | null;
    ownerTeam: string | null;
    dependencies: unknown;
    configSchema: unknown;
    defaultState: string;
    status: string;
    metadata: unknown;
    createdAt: Date;
    updatedAt: Date;
  }): Capability {
    return {
      id: capability.id,
      capabilityId: capability.capabilityId,
      name: capability.name,
      version: capability.version,
      description: capability.description || undefined,
      ownerTeam: capability.ownerTeam || undefined,
      dependencies: (capability.dependencies as string[]) || [],
      configSchema: capability.configSchema as Record<string, unknown> | undefined,
      defaultState: capability.defaultState as CapabilityState,
      status: capability.status as 'active' | 'deprecated' | 'archived',
      metadata: capability.metadata as Record<string, unknown> | undefined,
      createdAt: capability.createdAt,
      updatedAt: capability.updatedAt,
    };
  }
}

export const capabilityRegistryService = new CapabilityRegistryService();
