import { prisma } from '@/lib/prisma'
import { createPortainerClientForServer, PortainerContainer, ContainerInspectResponse } from './portainer-client'
import type { ContainerEventType } from '@prisma/client'
import { notificationService } from './notification-service'

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

export interface HealthCheckSummary {
  totalServers: number
  checkedServers: number
  failedServers: number
  eventsDetected: number
  crashes: number
  oomKills: number
  restarts: number
  duration: number
}

export interface ServerHealthResult {
  serverId: string
  success: boolean
  containersChecked: number
  eventsDetected: number
  error?: string
}

export interface StateChange {
  containerId: string
  containerName: string
  previousState: string
  currentState: string
  exitCode: number | null
  eventType: ContainerEventType
  details: string | null
}

export interface CrashSummary {
  crashes24h: number
  oomKills24h: number
  restarts24h: number
  recentCrashes: Array<{
    containerName: string
    serverNickname: string | null
    exitCode: number | null
    timestamp: Date
  }>
}

interface ContainerEventInput {
  containerId: string
  containerName: string
  eventType: ContainerEventType
  exitCode?: number | null
  details?: string | null
}

// ============================================================================
// Container Health Service
// ============================================================================

class ContainerHealthService {
  private static instance: ContainerHealthService

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

  // --------------------------------------------------------------------------
  // Health Checking Functions
  // --------------------------------------------------------------------------

  /**
   * Check all servers for container state changes
   */
  async checkAllServers(): Promise<HealthCheckSummary> {
    const startTime = Date.now()
    const summary: HealthCheckSummary = {
      totalServers: 0,
      checkedServers: 0,
      failedServers: 0,
      eventsDetected: 0,
      crashes: 0,
      oomKills: 0,
      restarts: 0,
      duration: 0,
    }

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

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

    for (const server of servers) {
      const result = await this.checkServerHealth(server.id)

      if (result.success) {
        summary.checkedServers++
        summary.eventsDetected += result.eventsDetected
      } else {
        summary.failedServers++
        console.error(`[HealthCheck] Failed to check server ${server.nickname || server.id}: ${result.error}`)
      }
    }

    // Count event types from recent events
    const recentEvents = await prisma.containerEvent.groupBy({
      by: ['eventType'],
      where: {
        timestamp: { gte: new Date(Date.now() - 5 * 60 * 1000) }, // Last 5 minutes
      },
      _count: true,
    })

    for (const event of recentEvents) {
      switch (event.eventType) {
        case 'CRASH':
          summary.crashes = event._count
          break
        case 'OOM_KILLED':
          summary.oomKills = event._count
          break
        case 'RESTART':
          summary.restarts = event._count
          break
      }
    }

    summary.duration = Date.now() - startTime
    console.log(`[HealthCheck] Completed: ${summary.checkedServers}/${summary.totalServers} servers, ${summary.eventsDetected} events detected in ${summary.duration}ms`)

    return summary
  }

  /**
   * Check single server's containers
   */
  async checkServerHealth(serverId: string): Promise<ServerHealthResult> {
    const result: ServerHealthResult = {
      serverId,
      success: false,
      containersChecked: 0,
      eventsDetected: 0,
    }

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

      // Get all containers with their current state
      const containers = await portainerClient.listContainers(true)
      result.containersChecked = containers.length

      // Detect state changes
      const stateChanges = await this.detectStateChanges(serverId, containers, portainerClient)

      // Record events for any state changes
      for (const change of stateChanges) {
        await this.recordContainerEvent(serverId, {
          containerId: change.containerId,
          containerName: change.containerName,
          eventType: change.eventType,
          exitCode: change.exitCode,
          details: change.details,
        })
        result.eventsDetected++
      }

      // Update state snapshots for all containers
      await this.updateStateSnapshots(serverId, containers)

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

    return result
  }

  /**
   * Detect crashes by comparing current state to last snapshot
   */
  async detectStateChanges(
    serverId: string,
    currentContainers: PortainerContainer[],
    portainerClient: ReturnType<typeof createPortainerClientForServer> extends Promise<infer T> ? NonNullable<T> : never
  ): Promise<StateChange[]> {
    const changes: StateChange[] = []

    // Get the most recent snapshot for each container
    const previousSnapshots = await prisma.containerStateSnapshot.findMany({
      where: { serverId },
      orderBy: { capturedAt: 'desc' },
      distinct: ['containerId'],
    })

    const snapshotMap = new Map(
      previousSnapshots.map(s => [s.containerId, s])
    )

    for (const container of currentContainers) {
      const containerName = this.getContainerName(container)
      const previousSnapshot = snapshotMap.get(container.Id)

      if (!previousSnapshot) {
        // New container, no previous state to compare
        continue
      }

      // Check for state changes
      if (previousSnapshot.state !== container.State) {
        let eventType: ContainerEventType | null = null
        let exitCode: number | null = null
        let details: string | null = null

        // Get detailed container info for exit code
        try {
          const inspectData = await portainerClient.getContainer(container.Id)
          exitCode = inspectData.State.ExitCode

          // Determine event type
          if (previousSnapshot.state === 'running') {
            if (container.State === 'exited' || container.State === 'dead') {
              if (inspectData.State.OOMKilled) {
                eventType = 'OOM_KILLED'
                details = 'Container was killed due to out of memory'
              } else if (exitCode !== 0) {
                eventType = 'CRASH'
                details = `Container exited with code ${exitCode}`
              } else {
                eventType = 'STOPPED'
                details = 'Container stopped gracefully'
              }
            }
          }

          // Check for restarts (container restarted since last check)
          if (container.State === 'running' && previousSnapshot.state === 'running') {
            // This might be a restart - we can check by comparing created timestamps
            // or restart count if available
            // For now, we'll skip restart detection as it's harder to detect reliably
          }
        } catch (e) {
          console.error(`[HealthCheck] Failed to inspect container ${container.Id}:`, e)
          // Still record the state change with what we know
          if (previousSnapshot.state === 'running' && (container.State === 'exited' || container.State === 'dead')) {
            eventType = 'CRASH'
            details = `Container state changed from ${previousSnapshot.state} to ${container.State}`
          }
        }

        if (eventType) {
          changes.push({
            containerId: container.Id,
            containerName,
            previousState: previousSnapshot.state,
            currentState: container.State,
            exitCode,
            eventType,
            details,
          })

          console.log(`[HealthCheck] Detected ${eventType} for ${containerName}: ${previousSnapshot.state} → ${container.State}`)
        }
      }
    }

    return changes
  }

  /**
   * Record a container event (crash, OOM, restart)
   */
  async recordContainerEvent(
    serverId: string,
    event: ContainerEventInput
  ): Promise<void> {
    // Check for duplicate events (same container, same type, within 5 minutes)
    const recentCutoff = new Date(Date.now() - 5 * 60 * 1000)
    const existingEvent = await prisma.containerEvent.findFirst({
      where: {
        serverId,
        containerId: event.containerId,
        eventType: event.eventType,
        timestamp: { gte: recentCutoff },
      },
    })

    if (existingEvent) {
      // Duplicate event, skip
      return
    }

    const createdEvent = await prisma.containerEvent.create({
      data: {
        serverId,
        containerId: event.containerId,
        containerName: event.containerName,
        eventType: event.eventType,
        exitCode: event.exitCode,
        details: event.details,
      },
    })

    // Send notifications for CRASH and OOM_KILLED events
    if (event.eventType === 'CRASH' || event.eventType === 'OOM_KILLED') {
      // Get server with client info for notification
      const server = await prisma.enterpriseServer.findUnique({
        where: { id: serverId },
        select: {
          clientId: true,
          nickname: true,
          netcupServerId: true,
        },
      })

      if (server) {
        // Fire and forget - don't block on notification
        notificationService
          .sendContainerCrashAlert(server.clientId, {
            ...createdEvent,
            server: {
              nickname: server.nickname,
              netcupServerId: server.netcupServerId,
            },
          })
          .catch((err) =>
            console.error('[Notification] Failed to send container crash alert:', err)
          )
      }
    }
  }

  /**
   * Update state snapshots for all containers on a server
   */
  private async updateStateSnapshots(
    serverId: string,
    containers: PortainerContainer[]
  ): Promise<void> {
    const now = new Date()

    for (const container of containers) {
      const containerName = this.getContainerName(container)

      // Upsert snapshot - we only keep the latest per container
      // The index allows us to query historical snapshots if needed
      await prisma.containerStateSnapshot.create({
        data: {
          serverId,
          containerId: container.Id,
          containerName,
          state: container.State,
          exitCode: null, // Exit code requires inspection, only set on state change
          capturedAt: now,
        },
      })
    }
  }

  // --------------------------------------------------------------------------
  // Dashboard Queries
  // --------------------------------------------------------------------------

  /**
   * Get crash summary for dashboard
   */
  async getCrashSummary(clientId: string, hours: number = 24): Promise<CrashSummary> {
    const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000)

    // Count events by type
    const eventCounts = await prisma.containerEvent.groupBy({
      by: ['eventType'],
      where: {
        server: { clientId },
        timestamp: { gte: cutoff },
      },
      _count: true,
    })

    const countMap = new Map(
      eventCounts.map(e => [e.eventType, e._count])
    )

    // Get recent crashes with details
    const recentCrashes = await prisma.containerEvent.findMany({
      where: {
        server: { clientId },
        eventType: 'CRASH',
        timestamp: { gte: cutoff },
      },
      include: {
        server: {
          select: { nickname: true },
        },
      },
      orderBy: { timestamp: 'desc' },
      take: 5,
    })

    return {
      crashes24h: countMap.get('CRASH') ?? 0,
      oomKills24h: countMap.get('OOM_KILLED') ?? 0,
      restarts24h: countMap.get('RESTART') ?? 0,
      recentCrashes: recentCrashes.map(c => ({
        containerName: c.containerName,
        serverNickname: c.server.nickname,
        exitCode: c.exitCode,
        timestamp: c.timestamp,
      })),
    }
  }

  /**
   * Get unacknowledged container events for a client
   */
  async getUnacknowledgedEvents(
    clientId: string,
    options?: {
      eventType?: ContainerEventType
      serverId?: string
      limit?: number
      offset?: number
    }
  ): Promise<{
    events: Awaited<ReturnType<typeof prisma.containerEvent.findMany>>
    total: number
  }> {
    const where = {
      server: { clientId },
      acknowledgedAt: null,
      ...(options?.eventType && { eventType: options.eventType }),
      ...(options?.serverId && { serverId: options.serverId }),
    }

    const [events, total] = await Promise.all([
      prisma.containerEvent.findMany({
        where,
        include: {
          server: {
            select: { nickname: true, netcupServerId: true },
          },
        },
        orderBy: { timestamp: 'desc' },
        take: options?.limit ?? 50,
        skip: options?.offset ?? 0,
      }),
      prisma.containerEvent.count({ where }),
    ])

    return { events, total }
  }

  /**
   * Acknowledge a container event
   */
  async acknowledgeEvent(eventId: string, userId: string): Promise<void> {
    await prisma.containerEvent.update({
      where: { id: eventId },
      data: {
        acknowledgedAt: new Date(),
        acknowledgedBy: userId,
      },
    })
  }

  /**
   * Acknowledge multiple events
   */
  async acknowledgeEvents(eventIds: string[], userId: string): Promise<number> {
    const result = await prisma.containerEvent.updateMany({
      where: { id: { in: eventIds } },
      data: {
        acknowledgedAt: new Date(),
        acknowledgedBy: userId,
      },
    })
    return result.count
  }

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

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

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

  /**
   * Delete old state snapshots (keep only last 7 days)
   */
  async cleanupOldSnapshots(): Promise<number> {
    const cutoff = new Date()
    cutoff.setDate(cutoff.getDate() - 7)

    const result = await prisma.containerStateSnapshot.deleteMany({
      where: {
        capturedAt: { lt: cutoff },
      },
    })

    return result.count
  }

  /**
   * Delete acknowledged events older than 30 days
   */
  async cleanupOldEvents(): Promise<number> {
    const cutoff = new Date()
    cutoff.setDate(cutoff.getDate() - 30)

    const result = await prisma.containerEvent.deleteMany({
      where: {
        acknowledgedAt: { not: null },
        timestamp: { lt: cutoff },
      },
    })

    return result.count
  }
}

export const containerHealthService = ContainerHealthService.getInstance()
