import { Injectable, NotFoundException, ConflictException, BadRequestException, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PluginRegistry } from '../../entities/plugin-registry.entity';
import { InstallPluginDto } from './dto/install-plugin.dto';
import { UpdatePluginConfigDto } from './dto/update-plugin-config.dto';
import { NatsService } from '../../services/nats.service';

interface UmodCacheEntry {
  data: unknown;
  timestamp: number;
}

@Injectable()
export class PluginsService {
  private readonly logger = new Logger(PluginsService.name);
  private readonly umodCache = new Map<string, UmodCacheEntry>();
  private readonly CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

  constructor(
    @InjectRepository(PluginRegistry)
    private readonly pluginRegistryRepo: Repository<PluginRegistry>,
    private readonly natsService: NatsService,
  ) {}

  async getPlugins(licenseId: string): Promise<PluginRegistry[]> {
    return this.pluginRegistryRepo.find({
      where: { license_id: licenseId },
      order: { installed_at: 'DESC' },
    });
  }

  async installPlugin(licenseId: string, dto: InstallPluginDto): Promise<PluginRegistry> {
    // Check if plugin already exists
    const existing = await this.pluginRegistryRepo.findOne({
      where: {
        license_id: licenseId,
        plugin_name: dto.plugin_name,
      },
    });

    if (existing) {
      throw new ConflictException(`Plugin ${dto.plugin_name} is already installed`);
    }

    const plugin = this.pluginRegistryRepo.create({
      license_id: licenseId,
      plugin_name: dto.plugin_name,
      umod_slug: dto.umod_slug,
      source: dto.source || 'manual',
      is_installed: true,
      is_loaded: false,
    });

    const saved = await this.pluginRegistryRepo.save(plugin);

    try {
      await this.natsService.publish(`corrosion.${licenseId}.cmd.server`, {
        action: 'plugin_install',
        plugin_name: dto.plugin_name,
        umod_slug: dto.umod_slug,
        timestamp: new Date().toISOString(),
      });
      this.logger.log(`Plugin install dispatched for ${dto.plugin_name} on license ${licenseId}`);
    } catch (err) {
      this.logger.error(`Failed to dispatch plugin install for ${dto.plugin_name} on license ${licenseId}: ${(err as Error).message}`);
    }

    return saved;
  }

  async uninstallPlugin(licenseId: string, pluginId: string): Promise<void> {
    const plugin = await this.pluginRegistryRepo.findOne({
      where: { id: pluginId, license_id: licenseId },
    });

    if (!plugin) {
      throw new NotFoundException(`Plugin ${pluginId} not found`);
    }

    await this.pluginRegistryRepo.delete({ id: pluginId, license_id: licenseId });

    await this.natsService.publish(`corrosion.${licenseId}.cmd.plugin`, {
      action: 'unload',
      plugin_name: plugin.plugin_name,
      timestamp: new Date().toISOString(),
    });
    this.logger.log(`Plugin uninstall dispatched for ${plugin.plugin_name} on license ${licenseId}`);
  }

  async reloadPlugin(
    licenseId: string,
    pluginId: string,
  ): Promise<{ reloaded: boolean; plugin_name: string }> {
    const plugin = await this.pluginRegistryRepo.findOne({
      where: { id: pluginId, license_id: licenseId },
    });

    if (!plugin) {
      throw new NotFoundException(`Plugin ${pluginId} not found`);
    }

    await this.natsService.publish(`corrosion.${licenseId}.cmd.plugin`, {
      action: 'reload',
      plugin_name: plugin.plugin_name,
      timestamp: new Date().toISOString(),
    });
    this.logger.log(`Plugin reload dispatched for ${plugin.plugin_name} on license ${licenseId}`);

    return { reloaded: true, plugin_name: plugin.plugin_name };
  }

  async updateConfig(
    licenseId: string,
    pluginId: string,
    dto: UpdatePluginConfigDto,
  ): Promise<PluginRegistry> {
    const plugin = await this.pluginRegistryRepo.findOne({
      where: { id: pluginId, license_id: licenseId },
    });

    if (!plugin) {
      throw new NotFoundException(`Plugin ${pluginId} not found`);
    }

    Object.assign(plugin, dto);
    plugin.updated_at = new Date();
    return this.pluginRegistryRepo.save(plugin);
  }

  async searchUmod(query: string): Promise<{ results: any[]; message: string }> {
    // Legacy stub — use browseUmod via GET /plugins/browse for real results
    return {
      results: [],
      message: 'Use GET /plugins/browse for uMod search',
    };
  }

  async browseUmod(query: string, page = 1, sort = 'downloads'): Promise<unknown> {
    const cacheKey = `${query ?? ''}:${page}:${sort}`;
    const cached = this.umodCache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) {
      this.logger.debug(`uMod cache hit for key "${cacheKey}"`);
      return cached.data;
    }

    const params = new URLSearchParams({
      page: String(page),
      sort,
      sortdir: 'desc',
      'categories[]': 'rust',
    });

    if (query?.trim()) {
      params.set('query', query.trim());
    }

    const url = `https://umod.org/plugins/search.json?${params.toString()}`;

    try {
      const response = await fetch(url, {
        headers: { 'User-Agent': 'CorrosionPanel/1.0' },
        signal: AbortSignal.timeout(8000),
      });

      if (!response.ok) {
        this.logger.warn(`uMod API returned ${response.status} for query "${query}"`);
        return { current_page: 1, data: [], last_page: 1, per_page: 20, total: 0 };
      }

      const data = await response.json();
      this.umodCache.set(cacheKey, { data, timestamp: Date.now() });
      this.logger.log(`uMod browse: query="${query}" page=${page} sort=${sort} → ${(data as any)?.total ?? '?'} results`);
      return data;
    } catch (err) {
      this.logger.error(`uMod browse failed for query "${query}": ${(err as Error).message}`);
      return { current_page: 1, data: [], last_page: 1, per_page: 20, total: 0 };
    }
  }

  async uploadPlugin(licenseId: string, file: Express.Multer.File): Promise<PluginRegistry> {
    // Validate extension
    const originalName = file.originalname ?? '';
    if (!originalName.toLowerCase().endsWith('.cs')) {
      throw new BadRequestException('Only .cs plugin files are accepted');
    }

    // Validate size (5 MB)
    const MAX_BYTES = 5 * 1024 * 1024;
    if (file.size > MAX_BYTES) {
      throw new BadRequestException('Plugin file exceeds the 5 MB limit');
    }

    // Derive plugin name from filename (strip .cs)
    const pluginName = originalName.replace(/\.cs$/i, '');

    // Check for duplicate
    const existing = await this.pluginRegistryRepo.findOne({
      where: { license_id: licenseId, plugin_name: pluginName },
    });

    if (existing) {
      throw new ConflictException(`Plugin "${pluginName}" is already installed`);
    }

    // Persist record
    const plugin = this.pluginRegistryRepo.create({
      license_id: licenseId,
      plugin_name: pluginName,
      source: 'manual',
      is_installed: true,
      is_loaded: false,
    });

    const saved = await this.pluginRegistryRepo.save(plugin);

    // Dispatch to companion agent via NATS
    try {
      const content = file.buffer.toString('base64');
      await this.natsService.publish(`corrosion.${licenseId}.cmd.server`, {
        action: 'plugin_upload',
        filename: originalName,
        content,
        timestamp: new Date().toISOString(),
      });
      this.logger.log(`Plugin upload dispatched: "${originalName}" (${file.size} bytes) for license ${licenseId}`);
    } catch (err) {
      this.logger.error(`NATS publish failed for plugin upload "${originalName}" on license ${licenseId}: ${(err as Error).message}`);
      // Don't fail the request — plugin record is saved, NATS delivery is best-effort
    }

    return saved;
  }
}
