import { prisma } from '@/lib/prisma'
import { createPortainerClientForServer, PortainerClient, PortainerContainer } from './portainer-client'
import { scanAndStoreErrors } from './error-detection-service'
import crypto from 'crypto'

// ============================================================================
// Types
// ============================================================================

export interface ScanSummary {
  totalServers: number
  scannedServers: number
  failedServers: number
  totalContainers: number
  scannedContainers: number
  totalErrorsFound: number
  duration: number
}

export interface ServerScanResult {
  serverId: string
  success: boolean
  containersScanned: number
  errorsFound: number
  error?: string
}

export interface ContainerScanResult {
  containerId: string
  containerName: string
  success: boolean
  errorsFound: number
  linesScanned: number
  error?: string
}

interface IncrementalLogResult {
  newContent: string
  totalLines: number
  newLinesCount: number
  logHash: string
}

// ============================================================================
// Log Scanning Service
// ============================================================================

class LogScanningService {
  private static instance: LogScanningService
  private static readonly LOG_TAIL_LINES = 500 // Fetch last 500 lines per scan
  private static readonly MAX_CONCURRENT_CONTAINERS = 5 // Limit concurrent container scans

  static getInstance(): LogScanningService {
    if (!LogScanningService.instance) {
      LogScanningService.instance = new LogScanningService()
    }
    return LogScanningService.instance
  }

  // --------------------------------------------------------------------------
  // Main Scanning Functions
  // --------------------------------------------------------------------------

  /**
   * Scan all active servers for all clients
   */
  async scanAllServers(): Promise<ScanSummary> {
    const startTime = Date.now()
    const summary: ScanSummary = {
      totalServers: 0,
      scannedServers: 0,
      failedServers: 0,
      totalContainers: 0,
      scannedContainers: 0,
      totalErrorsFound: 0,
      duration: 0,
    }

    // Get all active servers with their client ID
    const servers = await prisma.enterpriseServer.findMany({
      where: { isActive: true },
      select: {
        id: true,
        clientId: true,
        nickname: true,
      },
    })

    summary.totalServers = servers.length
    console.log(`[LogScan] Starting scan of ${servers.length} servers`)

    for (const server of servers) {
      const result = await this.scanServerContainers(server.id, server.clientId)

      if (result.success) {
        summary.scannedServers++
        summary.scannedContainers += result.containersScanned
        summary.totalErrorsFound += result.errorsFound
      } else {
        summary.failedServers++
        console.error(`[LogScan] Failed to scan server ${server.nickname || server.id}: ${result.error}`)
      }

      summary.totalContainers += result.containersScanned
    }

    summary.duration = Date.now() - startTime
    console.log(`[LogScan] Completed: ${summary.scannedServers}/${summary.totalServers} servers, ${summary.totalErrorsFound} errors found in ${summary.duration}ms`)

    return summary
  }

  /**
   * Scan all containers on a single server
   */
  async scanServerContainers(serverId: string, clientId?: string): Promise<ServerScanResult> {
    const result: ServerScanResult = {
      serverId,
      success: false,
      containersScanned: 0,
      errorsFound: 0,
    }

    try {
      // Get client ID if not provided
      if (!clientId) {
        const server = await prisma.enterpriseServer.findUnique({
          where: { id: serverId },
          select: { clientId: true },
        })
        if (!server) {
          result.error = 'Server not found'
          return result
        }
        clientId = server.clientId
      }

      // Check if client has any active error rules
      const ruleCount = await prisma.errorDetectionRule.count({
        where: { clientId, isActive: true },
      })

      if (ruleCount === 0) {
        // No rules to scan with, skip but consider it successful
        result.success = true
        console.log(`[LogScan] No active rules for client ${clientId}, skipping server ${serverId}`)
        return result
      }

      // Create Portainer client
      const portainerClient = await createPortainerClientForServer(serverId)
      if (!portainerClient) {
        result.error = 'Portainer credentials not configured'
        return result
      }

      // Get all containers
      const containers = await portainerClient.listContainers(true)
      console.log(`[LogScan] Found ${containers.length} containers on server ${serverId}`)

      // Scan containers in batches to limit concurrency
      const batches = this.chunkArray(containers, LogScanningService.MAX_CONCURRENT_CONTAINERS)

      for (const batch of batches) {
        const scanPromises = batch.map(container =>
          this.scanContainerLogs(
            serverId,
            container.Id,
            this.getContainerName(container),
            clientId!,
            portainerClient
          )
        )

        const batchResults = await Promise.all(scanPromises)

        for (const containerResult of batchResults) {
          if (containerResult.success) {
            result.containersScanned++
            result.errorsFound += containerResult.errorsFound
          }
        }
      }

      result.success = true
    } catch (error) {
      result.error = error instanceof Error ? error.message : 'Unknown error'
      console.error(`[LogScan] Error scanning server ${serverId}:`, error)
    }

    return result
  }

  /**
   * Scan a single container with incremental tracking
   */
  async scanContainerLogs(
    serverId: string,
    containerId: string,
    containerName: string,
    clientId: string,
    portainerClient: PortainerClient
  ): Promise<ContainerScanResult> {
    const result: ContainerScanResult = {
      containerId,
      containerName,
      success: false,
      errorsFound: 0,
      linesScanned: 0,
    }

    try {
      // Get incremental logs
      const logResult = await this.getIncrementalLogs(
        serverId,
        containerId,
        portainerClient
      )

      if (logResult.newLinesCount === 0) {
        // No new logs to scan
        result.success = true
        return result
      }

      result.linesScanned = logResult.newLinesCount

      // Scan the new content for errors
      const errorsCreated = await scanAndStoreErrors(
        serverId,
        containerId,
        containerName,
        logResult.newContent,
        clientId
      )

      result.errorsFound = errorsCreated
      result.success = true

      // Update scan position
      await this.updateScanPosition(
        serverId,
        containerId,
        logResult.totalLines,
        logResult.logHash
      )

      if (errorsCreated > 0) {
        console.log(`[LogScan] Found ${errorsCreated} new errors in ${containerName} (${containerId.substring(0, 12)})`)
      }
    } catch (error) {
      result.error = error instanceof Error ? error.message : 'Unknown error'
      console.error(`[LogScan] Error scanning container ${containerName}:`, error)
    }

    return result
  }

  // --------------------------------------------------------------------------
  // Incremental Log Tracking
  // --------------------------------------------------------------------------

  /**
   * Get only new log lines since last scan
   */
  private async getIncrementalLogs(
    serverId: string,
    containerId: string,
    portainerClient: PortainerClient
  ): Promise<IncrementalLogResult> {
    // Fetch logs
    const rawLogs = await portainerClient.getContainerLogs(
      containerId,
      LogScanningService.LOG_TAIL_LINES
    )

    const lines = rawLogs.split('\n').filter(line => line.trim())
    const totalLines = lines.length

    // Calculate hash of first 10 lines to detect log rotation
    const hashInput = lines.slice(0, 10).join('\n')
    const logHash = crypto.createHash('md5').update(hashInput).digest('hex')

    // Get previous scan position
    const position = await prisma.logScanPosition.findUnique({
      where: {
        serverId_containerId: { serverId, containerId },
      },
    })

    // Determine which lines are new
    let newContent: string
    let newLinesCount: number

    if (!position) {
      // First scan - process all lines
      newContent = lines.join('\n')
      newLinesCount = lines.length
    } else if (position.lastLogHash !== logHash) {
      // Log rotation detected - process all lines
      console.log(`[LogScan] Log rotation detected for container ${containerId.substring(0, 12)}`)
      newContent = lines.join('\n')
      newLinesCount = lines.length
    } else if (totalLines > position.lastLineCount) {
      // Normal case - only new lines since last scan
      const newLines = lines.slice(position.lastLineCount)
      newContent = newLines.join('\n')
      newLinesCount = newLines.length
    } else {
      // No new lines (or fewer lines than before, which might indicate truncation)
      newContent = ''
      newLinesCount = 0
    }

    return {
      newContent,
      totalLines,
      newLinesCount,
      logHash,
    }
  }

  /**
   * Update scan position after successful scan
   */
  private async updateScanPosition(
    serverId: string,
    containerId: string,
    lineCount: number,
    logHash: string
  ): Promise<void> {
    await prisma.logScanPosition.upsert({
      where: {
        serverId_containerId: { serverId, containerId },
      },
      create: {
        serverId,
        containerId,
        lastLineCount: lineCount,
        lastLogHash: logHash,
        lastScannedAt: new Date(),
      },
      update: {
        lastLineCount: lineCount,
        lastLogHash: logHash,
        lastScannedAt: new Date(),
      },
    })
  }

  // --------------------------------------------------------------------------
  // Helpers
  // --------------------------------------------------------------------------

  /**
   * Get clean container name from Portainer container object
   */
  private getContainerName(container: PortainerContainer): string {
    if (container.Names && container.Names.length > 0) {
      // Docker prefixes names with /, remove it
      return container.Names[0].replace(/^\//, '')
    }
    return container.Id.substring(0, 12)
  }

  /**
   * Chunk array into batches
   */
  private chunkArray<T>(array: T[], chunkSize: number): T[][] {
    const chunks: T[][] = []
    for (let i = 0; i < array.length; i += chunkSize) {
      chunks.push(array.slice(i, i + chunkSize))
    }
    return chunks
  }

  // --------------------------------------------------------------------------
  // Cleanup
  // --------------------------------------------------------------------------

  /**
   * Remove scan positions for deleted containers/servers
   * Note: This is handled automatically by Prisma cascading deletes
   * when the server is deleted. This function is kept for manual cleanup if needed.
   */
  async cleanupOrphanedPositions(): Promise<number> {
    // Positions are automatically deleted when server is deleted (CASCADE)
    // This function can be used to clean up positions for containers
    // that no longer exist on a server

    // Get all positions and check if their containers still exist
    // For now, we'll rely on the cascading delete and return 0
    return 0
  }
}

export const logScanningService = LogScanningService.getInstance()
