import {
  Controller,
  Get,
  Post,
  Patch,
  Delete,
  Body,
  Param,
  HttpCode,
  HttpStatus,
  NotFoundException,
  BadRequestException,
  Logger,
} from "@nestjs/common";
import type { InputJsonValue } from "@prisma/client/runtime/library";
import { PrismaService } from "../prisma/prisma.service";
import { LlmManagerService } from "./llm-manager.service";
import { CreateLlmProviderDto, UpdateLlmProviderDto, LlmProviderResponseDto } from "./dto";

/**
 * Controller for LLM provider administration.
 * Provides CRUD operations for managing LLM provider instances.
 *
 * @example
 * ```typescript
 * // List all providers
 * GET /llm/admin/providers
 *
 * // Create a new provider
 * POST /llm/admin/providers
 * {
 *   "providerType": "ollama",
 *   "displayName": "Local Ollama",
 *   "config": { "endpoint": "http://localhost:11434" },
 *   "isDefault": true
 * }
 *
 * // Test provider connection
 * POST /llm/admin/providers/:id/test
 *
 * // Reload providers from database
 * POST /llm/admin/reload
 * ```
 */
@Controller("llm/admin")
export class LlmProviderAdminController {
  private readonly logger = new Logger(LlmProviderAdminController.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly llmManager: LlmManagerService
  ) {}

  /**
   * List all LLM provider instances from the database.
   * Returns both enabled and disabled providers.
   *
   * @returns Array of all provider instances
   */
  @Get("providers")
  async listProviders(): Promise<LlmProviderResponseDto[]> {
    const providers = await this.prisma.llmProviderInstance.findMany({
      orderBy: { createdAt: "asc" },
    });

    return providers;
  }

  /**
   * Get a specific LLM provider instance by ID.
   *
   * @param id - Provider instance ID
   * @returns Provider instance
   * @throws {NotFoundException} If provider not found
   */
  @Get("providers/:id")
  async getProvider(@Param("id") id: string): Promise<LlmProviderResponseDto> {
    const provider = await this.prisma.llmProviderInstance.findUnique({
      where: { id },
    });

    if (!provider) {
      throw new NotFoundException(`LLM provider with ID ${id} not found`);
    }

    return provider;
  }

  /**
   * Create a new LLM provider instance.
   * If enabled, the provider will be automatically registered with the LLM manager.
   *
   * @param dto - Provider creation data
   * @returns Created provider instance
   * @throws {BadRequestException} If validation fails
   */
  @Post("providers")
  @HttpCode(HttpStatus.CREATED)
  async createProvider(@Body() dto: CreateLlmProviderDto): Promise<LlmProviderResponseDto> {
    // Create provider in database
    const provider = await this.prisma.llmProviderInstance.create({
      data: {
        providerType: dto.providerType,
        displayName: dto.displayName,
        userId: dto.userId ?? null,
        config: dto.config as InputJsonValue,
        isDefault: dto.isDefault ?? false,
        isEnabled: dto.isEnabled ?? true,
      },
    });

    // Register with LLM manager if enabled
    if (provider.isEnabled) {
      await this.llmManager.registerProvider(provider);
    }

    return provider;
  }

  /**
   * Update an existing LLM provider instance.
   * The provider will be unregistered and re-registered if it's enabled.
   *
   * @param id - Provider instance ID
   * @param dto - Provider update data
   * @returns Updated provider instance
   * @throws {NotFoundException} If provider not found
   */
  @Patch("providers/:id")
  async updateProvider(
    @Param("id") id: string,
    @Body() dto: UpdateLlmProviderDto
  ): Promise<LlmProviderResponseDto> {
    // Verify provider exists
    const existingProvider = await this.prisma.llmProviderInstance.findUnique({
      where: { id },
    });

    if (!existingProvider) {
      throw new NotFoundException(`LLM provider with ID ${id} not found`);
    }

    // Build update data with only provided fields
    const updateData: {
      displayName?: string;
      config?: InputJsonValue;
      isDefault?: boolean;
      isEnabled?: boolean;
    } = {};

    if (dto.displayName !== undefined) {
      updateData.displayName = dto.displayName;
    }
    if (dto.config !== undefined) {
      updateData.config = dto.config as InputJsonValue;
    }
    if (dto.isDefault !== undefined) {
      updateData.isDefault = dto.isDefault;
    }
    if (dto.isEnabled !== undefined) {
      updateData.isEnabled = dto.isEnabled;
    }

    // Update provider in database
    const updatedProvider = await this.prisma.llmProviderInstance.update({
      where: { id },
      data: updateData,
    });

    // Unregister old provider instance from manager
    await this.llmManager.unregisterProvider(id);

    // Re-register if still enabled
    if (updatedProvider.isEnabled) {
      await this.llmManager.registerProvider(updatedProvider);
    }

    return updatedProvider;
  }

  /**
   * Delete an LLM provider instance.
   * Cannot delete the default provider - set another provider as default first.
   *
   * @param id - Provider instance ID
   * @throws {NotFoundException} If provider not found
   * @throws {BadRequestException} If trying to delete default provider
   */
  @Delete("providers/:id")
  @HttpCode(HttpStatus.NO_CONTENT)
  async deleteProvider(@Param("id") id: string): Promise<void> {
    // Verify provider exists
    const provider = await this.prisma.llmProviderInstance.findUnique({
      where: { id },
    });

    if (!provider) {
      throw new NotFoundException(`LLM provider with ID ${id} not found`);
    }

    // Prevent deleting default provider
    if (provider.isDefault) {
      throw new BadRequestException(
        "Cannot delete the default provider. Set another provider as default first."
      );
    }

    // Unregister from manager
    await this.llmManager.unregisterProvider(id);

    // Delete from database
    await this.prisma.llmProviderInstance.delete({
      where: { id },
    });
  }

  /**
   * Test connection to an LLM provider.
   * Checks if the provider is healthy and can respond to requests.
   *
   * @param id - Provider instance ID
   * @returns Health check result
   * @throws {NotFoundException} If provider not found
   */
  @Post("providers/:id/test")
  async testProvider(@Param("id") id: string): Promise<{ healthy: boolean; error?: string }> {
    // Verify provider exists in database
    const provider = await this.prisma.llmProviderInstance.findUnique({
      where: { id },
    });

    if (!provider) {
      throw new NotFoundException(`LLM provider with ID ${id} not found`);
    }

    // Try to get provider from manager and check health
    try {
      const providerInstance = await this.llmManager.getProviderById(id);
      const health = await providerInstance.checkHealth();

      if (health.error !== undefined) {
        return {
          healthy: health.healthy,
          error: health.error,
        };
      }

      return {
        healthy: health.healthy,
      };
    } catch (error: unknown) {
      // Provider not loaded in manager (might be disabled)
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.warn(`Failed to test provider ${id}: ${errorMessage}`);

      return {
        healthy: false,
        error: "Provider not loaded in manager. Try reloading providers.",
      };
    }
  }

  /**
   * Reload all enabled providers from the database.
   * This will clear the current provider cache and reload fresh state.
   *
   * @returns Reload result with count of loaded providers
   */
  @Post("reload")
  async reloadProviders(): Promise<{ message: string; count: number }> {
    // Reload providers in manager
    await this.llmManager.reloadFromDatabase();

    // Get count of enabled providers
    const enabledProviders = await this.prisma.llmProviderInstance.findMany({
      where: { isEnabled: true },
    });

    return {
      message: "Providers reloaded successfully",
      count: enabledProviders.length,
    };
  }
}
