import fs from 'fs';
import path from 'path';
import { logger } from '../../utils/logger';

/**
 * Upgrade service — reads/writes JSON files from the shared upgrade directory.
 * The API container writes trigger files; the host systemd watcher reads them
 * and runs upgrade scripts. Status/progress/result files are written by the
 * host scripts and read by this service.
 */

const UPGRADE_DIR = path.resolve('/app/upgrade');
const STATUS_FILE = path.join(UPGRADE_DIR, 'status.json');
const PROGRESS_FILE = path.join(UPGRADE_DIR, 'progress.json');
const RESULT_FILE = path.join(UPGRADE_DIR, 'result.json');
const TRIGGER_FILE = path.join(UPGRADE_DIR, 'trigger.json');
const HISTORY_FILE = path.join(UPGRADE_DIR, 'history.json');
const TRIGGERED_BY_FILE = path.join(UPGRADE_DIR, 'triggered-by.txt');

// Stale threshold: if progress hasn't been updated in this many ms, assume crashed
const STALE_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes
const MAX_HISTORY_ENTRIES = 50;

export interface UpgradeStatus {
  branch: string;
  currentCommit: string;
  currentCommitFull: string;
  currentMessage: string;
  currentDate: string;
  remoteCommit: string | null;
  remoteCommitFull?: string | null;
  commitsBehind: number;
  changelog: Array<{
    hash: string;
    message: string;
    date: string;
    author: string;
  }>;
  breaking?: boolean;
  breakingReason?: string;
  checkedAt: string;
  error: string | null;
}

export interface UpgradeProgress {
  phase: number;
  phaseName: string;
  percentage: number;
  message: string;
  lastUpdate: string;
}

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

export interface WatcherHealth {
  healthy: boolean;
  reason?: string;
  pendingSince?: string;
}

interface TriggerPayload {
  action: 'check' | 'upgrade';
  branch?: string;
  skipBackup?: boolean;
  pullServices?: boolean;
  dryRun?: boolean;
  useRegistry?: boolean;
  triggeredAt: string;
  triggeredBy: string;
}

function readJsonFile<T>(filePath: string): T | null {
  try {
    if (!fs.existsSync(filePath)) return null;
    const raw = fs.readFileSync(filePath, 'utf-8');
    return JSON.parse(raw) as T;
  } catch (err) {
    logger.warn(`Failed to read ${path.basename(filePath)}:`, err);
    return null;
  }
}

function writeJsonFile(filePath: string, data: unknown): void {
  try {
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
  } catch (err) {
    logger.error(`Failed to write ${path.basename(filePath)}:`, err);
    throw err;
  }
}

function getStatus(): UpgradeStatus | null {
  return readJsonFile<UpgradeStatus>(STATUS_FILE);
}

/**
 * Watcher liveness heuristic. The host-side systemd watcher consumes and
 * DELETES trigger.json within ~1s of it appearing. If trigger.json exists
 * and is older than the threshold, the `.path` unit is almost certainly
 * wedged (e.g. StartLimitBurst latch) and the admin UI should surface it.
 */
const WATCHER_STALL_MS = 30 * 1000;

function getWatcherHealth(): WatcherHealth {
  try {
    if (!fs.existsSync(TRIGGER_FILE)) return { healthy: true };
    const mtimeMs = fs.statSync(TRIGGER_FILE).mtimeMs;
    const age = Date.now() - mtimeMs;
    if (age <= WATCHER_STALL_MS) return { healthy: true };
    return {
      healthy: false,
      reason: `Trigger file has been pending for ${Math.round(age / 1000)}s — host upgrade watcher may be stopped or failed`,
      pendingSince: new Date(mtimeMs).toISOString(),
    };
  } catch (err) {
    logger.warn('getWatcherHealth failed:', err);
    return { healthy: true };
  }
}

function getProgress(): UpgradeProgress | null {
  return readJsonFile<UpgradeProgress>(PROGRESS_FILE);
}

function getResult(): UpgradeResult | null {
  return readJsonFile<UpgradeResult>(RESULT_FILE);
}

function isRunning(): boolean {
  const progress = getProgress();
  if (!progress) return false;

  // Check if progress is stale (script probably crashed)
  const lastUpdate = new Date(progress.lastUpdate).getTime();
  const age = Date.now() - lastUpdate;
  if (age > STALE_THRESHOLD_MS) {
    logger.warn(`Upgrade progress is stale (${Math.round(age / 60000)}min old), assuming crashed`);
    return false;
  }

  return true;
}

function triggerCheck(triggeredBy: string, branch?: string): void {
  if (isRunning()) {
    throw new Error('An upgrade is already in progress');
  }

  const payload: TriggerPayload = {
    action: 'check',
    triggeredAt: new Date().toISOString(),
    triggeredBy,
  };
  if (branch) payload.branch = branch;

  writeJsonFile(TRIGGER_FILE, payload);
  logger.info(`Update check triggered by ${triggeredBy}`);
}

function triggerUpgrade(
  triggeredBy: string,
  options: { skipBackup?: boolean; pullServices?: boolean; dryRun?: boolean; branch?: string; useRegistry?: boolean } = {},
): void {
  if (isRunning()) {
    throw new Error('An upgrade is already in progress');
  }

  const payload: TriggerPayload = {
    action: 'upgrade',
    triggeredAt: new Date().toISOString(),
    triggeredBy,
    ...options,
  };

  writeJsonFile(TRIGGER_FILE, payload);
  logger.info(`Upgrade triggered by ${triggeredBy} (options: ${JSON.stringify(options)})`);
}

function clearResult(): void {
  try {
    if (fs.existsSync(RESULT_FILE)) {
      fs.unlinkSync(RESULT_FILE);
    }
  } catch (err) {
    logger.warn('Failed to clear result file:', err);
  }
}

function clearStaleProgress(): void {
  try {
    if (fs.existsSync(PROGRESS_FILE) && !isRunning()) {
      fs.unlinkSync(PROGRESS_FILE);
      logger.info('Cleaned up stale upgrade progress file');
    }
  } catch {
    // Ignore
  }
}

/** Archive a completed upgrade result to the persistent history file.
 *  Dedupes on completedAt so the bash-side success archival (upgrade.sh
 *  archive_success_to_history) and this API-side call can't double-record. */
function archiveResult(result: UpgradeResult): void {
  try {
    const history = readJsonFile<UpgradeResult[]>(HISTORY_FILE) || [];
    if (history[0]?.completedAt === result.completedAt) {
      logger.info('Skipping archive — most recent history entry has same completedAt');
      return;
    }
    history.unshift(result);
    // Trim to max entries
    if (history.length > MAX_HISTORY_ENTRIES) {
      history.length = MAX_HISTORY_ENTRIES;
    }
    writeJsonFile(HISTORY_FILE, history);
    logger.info(`Archived upgrade result to history (${history.length} entries)`);
  } catch (err) {
    logger.warn('Failed to archive upgrade result:', err);
  }
}

/** Get the list of past upgrade results (newest first). */
function getHistory(): UpgradeResult[] {
  return readJsonFile<UpgradeResult[]>(HISTORY_FILE) || [];
}

/** Read the triggered-by marker file (written by upgrade-watcher.sh). */
function getTriggeredBy(): string | null {
  try {
    if (!fs.existsSync(TRIGGERED_BY_FILE)) return null;
    return fs.readFileSync(TRIGGERED_BY_FILE, 'utf-8').trim() || null;
  } catch {
    return null;
  }
}

/** Remove the triggered-by marker file. */
function clearTriggeredBy(): void {
  try {
    if (fs.existsSync(TRIGGERED_BY_FILE)) {
      fs.unlinkSync(TRIGGERED_BY_FILE);
    }
  } catch {
    // Ignore
  }
}

export const upgradeService = {
  getStatus,
  getProgress,
  getResult,
  getWatcherHealth,
  isRunning,
  triggerCheck,
  triggerUpgrade,
  clearResult,
  clearStaleProgress,
  archiveResult,
  getHistory,
  getTriggeredBy,
  clearTriggeredBy,
};
