import { loadConfig } from './config.js';
import { Logger } from './logger.js';
import { TelegramNotifier } from './notifier.js';
import { shouldSkipChecks } from './deploy-lock.js';
import { runAllChecks, checkApiEndpoints } from './checker.js';
import { TaskQueue } from './task-queue.js';
import { attemptAutoFix } from './auto-fix.js';
import { addToBacklog } from './dev-backlog.js';
import type { ServiceConfig, CheckResult } from './types.js';
import {
  getState,
  recordSuccess,
  recordFailure,
  resetIncident,
  isFlapping,
  shouldSendAlert,
  markAlertSent,
  markFlappingAlertSent,
  canAutoFix,
  recordAutoFix,
  wasJustRecovered,
} from './state.js';

const config = loadConfig();
const logger = new Logger(config.logDir);
const notifier = new TelegramNotifier(config.telegram.botToken, config.telegram.chatId);
const taskQueue = new TaskQueue(config.incidentDir);

let deployPauseNotified = false;
let lastCleanup = Date.now();
const reportedApiErrors = new Set<string>(); // Track reported API errors to avoid repeats

async function processResult(
  result: CheckResult,
  service: ServiceConfig,
): Promise<void> {
  const state = getState(result.serviceId);

  if (result.ok) {
    // --- SERVICE IS UP ---
    const justRecovered = wasJustRecovered(result.serviceId);
    recordSuccess(result.serviceId);

    if (justRecovered) {
      const downtimeMs = state.firstFailureAt
        ? Date.now() - state.firstFailureAt
        : 0;

      const msg = notifier.formatRecovered(result.serviceName, downtimeMs);
      await notifier.send(msg, logger);

      if (state.incidentId) {
        taskQueue.resolveIncident(state.incidentId, logger);
        taskQueue.addNotification(state.incidentId, 'recovered');
      }

      resetIncident(result.serviceId);
      logger.info(`Service recovered: ${result.serviceId}`);
    }
    return;
  }

  // --- SERVICE IS DOWN ---
  recordFailure(result.serviceId);
  logger.warn(`Service check failed: ${result.serviceId}`, {
    error: result.error,
    consecutiveFailures: state.consecutiveFailures,
  });

  // Check for flapping
  if (isFlapping(result.serviceId, config.flapWindowMs, config.flapThreshold)) {
    if (!state.flappingAlertSent) {
      const msg = notifier.formatFlapping(result.serviceName);
      await notifier.send(msg, logger);
      markFlappingAlertSent(result.serviceId);
    }
    return;
  }

  // Not enough failures yet — skip
  if (state.consecutiveFailures < config.consecutiveFailuresBeforeAlert) {
    return;
  }

  // Create incident if none exists
  if (!state.incidentId) {
    const incidentId = taskQueue.createIncident(
      result.serviceId,
      result.serviceName,
      result.error ?? 'Unknown error',
      service.critical,
      logger,
    );
    state.incidentId = incidentId;
  }

  // Attempt auto-fix
  if (canAutoFix(result.serviceId, config.autoFixMax, config.autoFixCooldownMs)) {
    const fixResult = attemptAutoFix(service, logger);

    if (fixResult.attempted) {
      recordAutoFix(result.serviceId);
      taskQueue.addAutoFixAttempt(state.incidentId!, fixResult, logger);

      const fixMsg = notifier.formatAutoFix(
        result.serviceName,
        fixResult.action,
        fixResult.success,
        fixResult.output,
      );
      await notifier.send(fixMsg, logger);
      taskQueue.addNotification(state.incidentId!, 'auto-fix');
    }
  }

  // Send alert (with cooldown)
  if (shouldSendAlert(result.serviceId, config.consecutiveFailuresBeforeAlert, config.cooldownMs)) {
    const msg = notifier.formatServiceDown(
      result.serviceName,
      result.error ?? 'Unknown error',
      state.consecutiveFailures,
      service.critical,
    );
    await notifier.send(msg, logger);
    markAlertSent(result.serviceId);

    if (state.incidentId) {
      taskQueue.addNotification(state.incidentId, 'alert');
    }
  }
}

async function checkCycle(): Promise<void> {
  try {
    // Check deploy lock
    if (shouldSkipChecks(config.deployLockPath, config.deployGracePeriodMs, logger)) {
      if (!deployPauseNotified) {
        await notifier.send(notifier.formatDeployPaused(), logger);
        deployPauseNotified = true;
      }
      return;
    }
    deployPauseNotified = false;

    // Run all health checks
    const results = await runAllChecks(config.services, logger);

    // Check for multiple simultaneous failures
    const failedCount = Array.from(results.values()).filter((r) => !r.ok).length;
    if (failedCount >= 3) {
      logger.error(`Multiple services down: ${failedCount}/${results.size}`);
    }

    // Process each result
    const serviceMap = new Map(config.services.map((s) => [s.id, s]));
    for (const [serviceId, result] of results) {
      const service = serviceMap.get(serviceId);
      if (service) {
        await processResult(result, service);
      }
    }

    // API endpoint checks — categorize by error type
    const pendingDevAsks: { service: ServiceConfig; endpoints: { endpoint: string; page: string }[] }[] = [];
    for (const service of config.services) {
      if (!service.apiEndpoints || service.apiEndpoints.length === 0) continue;

      const apiResult = await checkApiEndpoints(service, service.apiEndpoints, logger);

      // 404 — newly discovered missing endpoints → add to backlog, collect for asking
      if (apiResult.newMissing.length > 0) {
        addToBacklog(service.id, service.name, apiResult.newMissing, logger);
        pendingDevAsks.push({ service, endpoints: apiResult.newMissing });
      }

      // 500/403 — report once per unique set of errors
      const newServerErrors = apiResult.serverErrors.filter(
        (ep) => !reportedApiErrors.has(`${service.id}:500:${ep.endpoint}`),
      );
      const newForbidden = apiResult.forbidden.filter(
        (ep) => !reportedApiErrors.has(`${service.id}:403:${ep.endpoint}`),
      );

      if (newServerErrors.length > 0 || newForbidden.length > 0) {
        const msg = notifier.formatApiErrors(service.name, newServerErrors, newForbidden);
        await notifier.send(msg, logger);

        // Mark as reported
        for (const ep of newServerErrors) {
          reportedApiErrors.add(`${service.id}:500:${ep.endpoint}`);
        }
        for (const ep of newForbidden) {
          reportedApiErrors.add(`${service.id}:403:${ep.endpoint}`);
        }

        // Also add 403s to backlog as dev tasks (permission misconfiguration)
        if (newForbidden.length > 0) {
          addToBacklog(service.id, service.name, newForbidden, logger);
        }
      }

      // Log server errors as warnings
      if (apiResult.serverErrors.length > 0) {
        logger.warn(`API server errors: ${service.id}`, {
          endpoints: apiResult.serverErrors.map((e) => `${e.endpoint} (${e.status})`),
        });
      }

      // Clear reported errors for endpoints that now work
      if (apiResult.resolved.length > 0) {
        for (const ep of apiResult.resolved) {
          reportedApiErrors.delete(`${service.id}:500:${ep}`);
          reportedApiErrors.delete(`${service.id}:403:${ep}`);
        }
        const msg = notifier.formatEndpointResolved(service.name, apiResult.resolved);
        await notifier.send(msg, logger);
      }
    }

    // Log summary
    const okCount = Array.from(results.values()).filter((r) => r.ok).length;
    logger.info(`Check cycle complete: ${okCount}/${results.size} services OK`);

    // Ask about dev tasks sequentially (bridge handles one question at a time)
    if (pendingDevAsks.length > 0) {
      processDevAsks(pendingDevAsks);
    }
  } catch (err) {
    logger.error('Check cycle failed', {
      error: err instanceof Error ? err.message : String(err),
    });
  }
}

function processDevAsks(
  asks: { service: ServiceConfig; endpoints: { endpoint: string; page: string }[] }[],
): void {
  // Run sequentially in background (fire-and-forget)
  (async () => {
    for (const { service, endpoints } of asks) {
      const endpointList = endpoints
        .map((ep) => `  • ${ep.endpoint} → страница ${ep.page}`)
        .join('\n');

      const question =
        `📋 ЗАДАЧА НА РАЗРАБОТКУ\n\n` +
        `Сервис: ${service.name}\n` +
        `Найдены нереализованные API-эндпоинты:\n${endpointList}\n\n` +
        `Добавлено в бэклог: ${endpoints.length} задач\n\n` +
        `Запустить доработку? (да/нет)`;

      await askAndCreateTask(service, endpoints, question);
    }
  })();
}

async function askAndCreateTask(
  service: ServiceConfig,
  endpoints: { endpoint: string; page: string }[],
  question: string,
): Promise<void> {
  try {
    const answer = await notifier.askViaBridge(question, logger, 120_000);

    if (!answer) {
      logger.info(`No answer for dev task: ${service.id} (timeout or error)`);
      return;
    }

    const isConfirm = /^(да|yes|ок|ok|запускай|давай|го|поехали|run|start|запустить|запусти)/i.test(answer.trim());

    if (isConfirm) {
      const endpointList = endpoints
        .map((ep) => `- ${ep.endpoint} (используется на странице ${ep.page})`)
        .join('\n');

      const taskMessage =
        `Реализуй недостающие API-эндпоинты для сервиса ${service.name}.\n\n` +
        `Список эндпоинтов:\n${endpointList}\n\n` +
        `Каждый эндпоинт сейчас возвращает 404. Нужно создать контроллеры и сервисы ` +
        `в apps/api/src/modules/ по аналогии с существующими модулями.\n` +
        `После реализации watchdog автоматически обнаружит, что эндпоинты заработали.`;

      const created = await notifier.createTaskViaBridge(taskMessage, logger);
      if (created) {
        await notifier.send(
          `✅ *Задача на разработку запущена*\nСервис: ${service.name}\nЭндпоинтов: ${endpoints.length}`,
          logger,
        );
      }
    } else {
      logger.info(`Dev task declined for ${service.id}: ${answer}`);
      await notifier.send(`⏸ Задача отложена: ${service.name}`, logger);
    }
  } catch (err) {
    logger.error('askAndCreateTask failed', {
      error: err instanceof Error ? err.message : String(err),
    });
  }
}

async function periodicCleanup(): Promise<void> {
  const oneDay = 24 * 60 * 60 * 1000;
  if (Date.now() - lastCleanup > oneDay) {
    taskQueue.cleanOldIncidents(30, logger);
    lastCleanup = Date.now();
  }
}

async function main(): Promise<void> {
  logger.info('Watchdog starting', {
    interval: config.checkIntervalMs,
    services: config.services.map((s) => s.id),
  });

  await notifier.send(notifier.formatStarted(config.checkIntervalMs), logger);

  // Run first check immediately
  await checkCycle();

  // Schedule periodic checks
  setInterval(async () => {
    await checkCycle();
    await periodicCleanup();
  }, config.checkIntervalMs);

  logger.info('Watchdog is running');
}

// Handle graceful shutdown
process.on('SIGINT', () => {
  logger.info('Watchdog stopping (SIGINT)');
  process.exit(0);
});

process.on('SIGTERM', () => {
  logger.info('Watchdog stopping (SIGTERM)');
  process.exit(0);
});

main().catch((err) => {
  console.error('Watchdog fatal error:', err);
  process.exit(1);
});
