import { exec as execCb } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
import { UpgradeStatus, AuditAction, InstanceStatus, Prisma } from '@prisma/client';
import { prisma } from '../lib/prisma';
import { logger } from '../utils/logger';
import { createEvent } from './event.service';

const exec = promisify(execCb);

const UPGRADE_TIMEOUT = 600_000; // 10 minutes
const PROGRESS_POLL_INTERVAL = 2_000; // 2 seconds

// ─── Update Check ─────────────────────────────────────────────────

export interface UpdateStatus {
  branch: string;
  currentCommit: string;
  currentMessage?: string;
  remoteCommit: string | null;
  commitsBehind: number;
  changelog: Array<{ hash: string; message: string; date: string; author: string }>;
  checkedAt: string;
  error: string | null;
}

/**
 * Check for available updates by running upgrade-check.sh in the instance's basePath.
 * Falls back to reading an existing status.json if the script isn't available.
 */
export async function checkForUpdates(instanceId: string): Promise<UpdateStatus> {
  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new Error('Instance not found');

  const basePath = instance.basePath;
  const statusFile = path.join(basePath, 'data', 'upgrade', 'status.json');
  const scriptPath = path.join(basePath, 'scripts', 'upgrade-check.sh');

  // Try to run upgrade-check.sh
  try {
    await fs.access(scriptPath);
    await exec(`bash "${scriptPath}"`, {
      cwd: basePath,
      timeout: 30_000,
      env: { ...process.env, COMPOSE_ANSI: 'never' },
    });
  } catch (err) {
    logger.warn(`[upgrade] upgrade-check.sh failed for ${instance.slug}: ${(err as Error).message}`);
    // Script may have still written status.json before failing — try reading it
  }

  // Read status.json
  try {
    const raw = await fs.readFile(statusFile, 'utf-8');
    const status = JSON.parse(raw) as UpdateStatus;
    return status;
  } catch {
    // If no status.json exists, try to gather basic git info
    try {
      const { stdout: branch } = await exec('git rev-parse --abbrev-ref HEAD', { cwd: basePath, timeout: 5_000 });
      const { stdout: commit } = await exec('git rev-parse --short HEAD', { cwd: basePath, timeout: 5_000 });
      return {
        branch: branch.trim(),
        currentCommit: commit.trim(),
        remoteCommit: null,
        commitsBehind: 0,
        changelog: [],
        checkedAt: new Date().toISOString(),
        error: 'Could not check for remote updates',
      };
    } catch {
      return {
        branch: instance.gitBranch,
        currentCommit: instance.gitCommit || 'unknown',
        remoteCommit: null,
        commitsBehind: 0,
        changelog: [],
        checkedAt: new Date().toISOString(),
        error: 'Could not determine version info (no .git directory?)',
      };
    }
  }
}

// ─── Upgrade Orchestration ────────────────────────────────────────

export interface StartUpgradeOptions {
  skipBackup?: boolean;
  useRegistry?: boolean;
  branch?: string;
}

/**
 * Start an upgrade for an instance. Returns the created InstanceUpgrade record.
 * The actual upgrade runs asynchronously (fire-and-forget).
 */
export async function startUpgrade(
  instanceId: string,
  userId: string,
  ipAddress?: string,
  options?: StartUpgradeOptions
) {
  const instance = await prisma.instance.findUnique({ where: { id: instanceId } });
  if (!instance) throw new Error('Instance not found');

  if (instance.status !== InstanceStatus.RUNNING && instance.status !== InstanceStatus.STOPPED) {
    throw new Error(`Cannot upgrade instance in ${instance.status} state`);
  }

  // Check for in-progress upgrades
  const active = await prisma.instanceUpgrade.findFirst({
    where: {
      instanceId,
      status: { in: [UpgradeStatus.PENDING, UpgradeStatus.IN_PROGRESS] },
    },
  });
  if (active) {
    throw new Error('An upgrade is already in progress for this instance');
  }

  // Get current commit for tracking
  let currentCommit: string | null = null;
  try {
    const { stdout } = await exec('git rev-parse --short HEAD', {
      cwd: instance.basePath,
      timeout: 5_000,
    });
    currentCommit = stdout.trim();
  } catch {
    // Non-critical — may be a release install without .git
  }

  const branch = options?.branch || instance.gitBranch;

  // Create upgrade record
  const upgrade = await prisma.instanceUpgrade.create({
    data: {
      instanceId,
      status: UpgradeStatus.PENDING,
      previousCommit: currentCommit,
      branch,
      triggeredById: userId,
    },
  });

  // Audit log
  await prisma.auditLog.create({
    data: {
      userId,
      instanceId,
      action: AuditAction.INSTANCE_UPGRADE,
      details: {
        upgradeId: upgrade.id,
        previousCommit: currentCommit,
        branch,
        options: options || {},
      } as unknown as Prisma.InputJsonValue,
      ipAddress,
    },
  });

  // Fire-and-forget: run the upgrade asynchronously
  runUpgrade(upgrade.id, instance.basePath, instance.slug, options).catch((err) => {
    logger.error(`[upgrade] Upgrade orchestration failed for ${instance.slug}: ${err}`);
  });

  return upgrade;
}

/**
 * Async upgrade runner. Runs upgrade.sh and polls progress.
 */
async function runUpgrade(
  upgradeId: string,
  basePath: string,
  slug: string,
  options?: StartUpgradeOptions
) {
  const progressFile = path.join(basePath, 'data', 'upgrade', 'progress.json');
  const resultFile = path.join(basePath, 'data', 'upgrade', 'result.json');
  const scriptPath = path.join(basePath, 'scripts', 'upgrade.sh');

  // Ensure data/upgrade directory exists
  await fs.mkdir(path.join(basePath, 'data', 'upgrade'), { recursive: true });

  // Clean up any stale progress/result files from previous runs
  await fs.rm(progressFile, { force: true });
  await fs.rm(resultFile, { force: true });

  // Mark as IN_PROGRESS
  await prisma.instanceUpgrade.update({
    where: { id: upgradeId },
    data: {
      status: UpgradeStatus.IN_PROGRESS,
      progressMessage: 'Starting upgrade...',
    },
  });

  // Build command flags
  const flags: string[] = ['--api-mode', '--force'];
  if (options?.skipBackup) flags.push('--skip-backup');
  if (options?.useRegistry) flags.push('--use-registry');
  if (options?.branch) flags.push('--branch', options.branch);

  // Start progress polling
  let pollTimer: NodeJS.Timeout | null = null;
  pollTimer = setInterval(async () => {
    try {
      const raw = await fs.readFile(progressFile, 'utf-8');
      const progress = JSON.parse(raw);
      await prisma.instanceUpgrade.update({
        where: { id: upgradeId },
        data: {
          currentPhase: progress.phase || 0,
          phaseName: progress.phaseName || null,
          percentage: progress.percentage || 0,
          progressMessage: progress.message || null,
        },
      });
    } catch {
      // progress.json may not exist yet or be mid-write
    }
  }, PROGRESS_POLL_INTERVAL);

  try {
    // Run upgrade.sh
    await exec(`bash "${scriptPath}" ${flags.join(' ')}`, {
      cwd: basePath,
      timeout: UPGRADE_TIMEOUT,
      maxBuffer: 10 * 1024 * 1024,
      env: { ...process.env, COMPOSE_ANSI: 'never' },
    });

    // Read result
    const result = await readResultFile(resultFile);

    // Read log tail
    const logTail = await readLatestLogTail(basePath);

    // Get new commit
    let newCommit: string | null = null;
    try {
      const { stdout } = await exec('git rev-parse --short HEAD', { cwd: basePath, timeout: 5_000 });
      newCommit = stdout.trim();
    } catch { /* ignore */ }

    // Update the upgrade record
    await prisma.instanceUpgrade.update({
      where: { id: upgradeId },
      data: {
        status: result.success ? UpgradeStatus.COMPLETED : UpgradeStatus.FAILED,
        newCommit: result.newCommit || newCommit,
        commitCount: result.commitCount || 0,
        percentage: 100,
        phaseName: 'Complete',
        progressMessage: result.message || 'Upgrade completed',
        durationSeconds: result.durationSeconds || null,
        warnings: result.warnings?.length ? result.warnings : undefined,
        errorMessage: result.success ? null : (result.message || 'Upgrade failed'),
        log: logTail,
        completedAt: new Date(),
      },
    });

    // Update instance gitCommit
    if (newCommit) {
      await prisma.instance.update({
        where: { id: (await prisma.instanceUpgrade.findUnique({ where: { id: upgradeId } }))!.instanceId },
        data: { gitCommit: newCommit },
      });
    }

    if (!result.success) {
      // Create error event
      const upgrade = await prisma.instanceUpgrade.findUnique({ where: { id: upgradeId } });
      if (upgrade) {
        await createEvent(
          upgrade.instanceId,
          'ERROR',
          'upgrade',
          'Upgrade failed',
          result.message || 'The upgrade process failed. Check logs for details.',
          { upgradeId, previousCommit: upgrade.previousCommit, warnings: result.warnings }
        );
      }
    }

    logger.info(`[upgrade] ${slug}: Upgrade ${result.success ? 'completed' : 'failed'}`);
  } catch (err) {
    const errorMsg = (err as Error).message;
    const isTimeout = errorMsg.includes('timed out');

    // Read whatever result/progress we have
    const result = await readResultFile(resultFile);
    const logTail = await readLatestLogTail(basePath);

    await prisma.instanceUpgrade.update({
      where: { id: upgradeId },
      data: {
        status: UpgradeStatus.FAILED,
        errorMessage: isTimeout ? 'Upgrade timed out after 10 minutes' : errorMsg.slice(0, 2000),
        progressMessage: 'Failed',
        log: logTail,
        completedAt: new Date(),
        durationSeconds: result.durationSeconds || null,
      },
    });

    // Create error event
    const upgrade = await prisma.instanceUpgrade.findUnique({ where: { id: upgradeId } });
    if (upgrade) {
      await createEvent(
        upgrade.instanceId,
        'ERROR',
        'upgrade',
        isTimeout ? 'Upgrade timed out' : 'Upgrade failed',
        isTimeout ? 'The upgrade process timed out after 10 minutes.' : errorMsg.slice(0, 500),
        { upgradeId }
      );

      // Set instance to ERROR state
      await prisma.instance.update({
        where: { id: upgrade.instanceId },
        data: {
          status: InstanceStatus.ERROR,
          statusMessage: `Upgrade failed: ${isTimeout ? 'timeout' : errorMsg.slice(0, 200)}`,
        },
      });
    }

    logger.error(`[upgrade] ${slug}: Upgrade failed: ${errorMsg}`);
  } finally {
    if (pollTimer) clearInterval(pollTimer);
  }
}

// ─── File Readers ─────────────────────────────────────────────────

interface UpgradeResult {
  success: boolean;
  message: string;
  previousCommit?: string;
  newCommit?: string;
  commitCount?: number;
  durationSeconds?: number;
  warnings?: string[];
}

async function readResultFile(resultFile: string): Promise<UpgradeResult> {
  try {
    const raw = await fs.readFile(resultFile, 'utf-8');
    return JSON.parse(raw);
  } catch {
    return { success: false, message: 'No result file found' };
  }
}

async function readLatestLogTail(basePath: string): Promise<string | null> {
  try {
    const logDir = path.join(basePath, 'logs');
    const files = await fs.readdir(logDir);
    const upgradeLog = files
      .filter((f) => f.startsWith('upgrade-'))
      .sort()
      .pop();
    if (!upgradeLog) return null;

    const content = await fs.readFile(path.join(logDir, upgradeLog), 'utf-8');
    // Return last 5000 chars to keep DB storage reasonable
    return content.slice(-5000);
  } catch {
    return null;
  }
}

// ─── Query Functions ──────────────────────────────────────────────

/**
 * Get the current/latest upgrade progress for an instance.
 */
export async function getUpgradeStatus(instanceId: string) {
  return prisma.instanceUpgrade.findFirst({
    where: { instanceId },
    orderBy: { startedAt: 'desc' },
    include: {
      triggeredBy: { select: { id: true, name: true, email: true } },
    },
  });
}

/**
 * Get paginated upgrade history for an instance.
 */
export async function getUpgradeHistory(instanceId: string, page = 1, limit = 20) {
  const [data, total] = await Promise.all([
    prisma.instanceUpgrade.findMany({
      where: { instanceId },
      orderBy: { startedAt: 'desc' },
      skip: (page - 1) * limit,
      take: limit,
      include: {
        triggeredBy: { select: { id: true, name: true, email: true } },
      },
    }),
    prisma.instanceUpgrade.count({ where: { instanceId } }),
  ]);

  return { data, total, page, limit };
}
