'use strict';

//
// AutoPilot — per-connection self-healing page + API.
//   GET  /autopilot/:connId                     — page shell (requireAuth)
//   GET  /api/autopilot/:connectionId/status    — schedule + remediation + log
//   POST /api/autopilot/:connectionId/save       — persist settings (tier-gated)
//   GET  /api/autopilot/:connectionId/log        — paginated activity log
//
// API param is :connectionId (NOT :connId) because requireConnectionOwner reads
// req.params.id || req.params.connectionId. The page route keeps :connId — it
// only requireAuth's and serves a static shell; the data routes enforce ownership.
// The router uses ABSOLUTE paths, so server.js mounts it ONCE at '/'.
//

const express = require('express');
const path    = require('path');
const pool    = require('../db/index');
const { requireAuth, requireConnectionOwner, ADMIN_EMAILS } = require('../middleware/auth');
const { HEARTBEAT_SERVICES, RESTARTABLE_OPS } = require('../services/auto-remediation');

const router = express.Router();

// Auto-remediation is Business+ (Business, Enterprise, Managed Services).
const REMEDIATION_TIERS = ['business', 'enterprise', 'managed_services'];

// req.user has no is_admin field (requireAuth attaches id/email/name/... only);
// admin in this codebase is ADMIN_EMAILS membership, same check requireAdmin and
// the other routes use. This is the real admin bypass for the tier gate.
function isAdmin(req) {
  return ADMIN_EMAILS.has(((req.user && req.user.email) || '').toLowerCase());
}

async function userTier(userId) {
  const { rows } = await pool.query(
    `SELECT plan_tier FROM user_credits WHERE user_id = $1`, [userId]
  );
  return (rows[0] && rows[0].plan_tier) || 'free';
}

// ── Page shell ───────────────────────────────────────────────────────────────
router.get('/autopilot/:connId', requireAuth, (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'public', 'autopilot.html'));
});

// ── GET status ───────────────────────────────────────────────────────────────
router.get('/api/autopilot/:connectionId/status', requireAuth, requireConnectionOwner, async (req, res) => {
  const connId = parseInt(req.params.connectionId, 10);
  try {
    const [schedRow, connRow, recentLog, heartbeatState, tier] = await Promise.all([
      pool.query(
        `SELECT enabled, cadence_minutes, alert_email, severity_threshold,
                auto_create_tickets, snoozed_until, next_run_at, last_run_at,
                auto_remediate, remediate_threshold, remediate_services
         FROM connection_schedules WHERE connection_id = $1`,
        [connId]
      ),
      pool.query(
        `SELECT id, name, server_type, connection_type, apps_pwd_enc, weblogic_pwd_enc
         FROM oracle_connections WHERE id = $1`,
        [connId]
      ),
      pool.query(
        `SELECT trigger_path, triggered_by, consecutive_count, op_key, op_label,
                outcome, exit_code, stdout_tail, tuneops_ticket_id, executed_at
         FROM auto_remediation_log
         WHERE connection_id = $1
         ORDER BY executed_at DESC LIMIT 20`,
        [connId]
      ),
      pool.query(
        `SELECT op_key, consecutive_down, last_checked_at, last_status
         FROM service_heartbeat_state WHERE connection_id = $1`,
        [connId]
      ),
      userTier(req.user.id),
    ]);

    const sched = schedRow.rows[0] || {};
    const conn  = connRow.rows[0];
    if (!conn) return res.status(404).json({ error: 'Connection not found' });

    // Latest completed health check score (status value is 'completed', not 'complete').
    const { rows: hcRows } = await pool.query(
      `SELECT overall_score, completed_at FROM health_checks
       WHERE connection_id = $1 AND status = 'completed'
       ORDER BY completed_at DESC NULLS LAST LIMIT 1`,
      [connId]
    );

    res.json({
      connection: { id: conn.id, name: conn.name, server_type: conn.server_type },
      // Credential presence — drives the "password not configured" warnings on the
      // managed-server (WebLogic) and Concurrent Manager (APPS) toggles.
      has_apps_pwd: !!(conn.apps_pwd_enc && conn.apps_pwd_enc !== ''),
      has_weblogic_pwd: !!(conn.weblogic_pwd_enc && conn.weblogic_pwd_enc !== ''),
      tier_ok: isAdmin(req) || REMEDIATION_TIERS.includes(tier),
      schedule: {
        enabled: !!sched.enabled,
        cadence_minutes: sched.cadence_minutes || 60,
        alert_email: sched.alert_email || '',
        severity_threshold: sched.severity_threshold || 'amber',
        auto_create_tickets: sched.auto_create_tickets !== false,
        snoozed_until: sched.snoozed_until || null,
        // next_run_at is maintained on connection_schedules by the scheduler
        // (advanceSchedule) and set on enable by the save route below. last_run_at
        // is the last autonomous run; fall back to the latest completed health
        // check so it aligns with the score shown and isn't blank after a manual run.
        next_run_at: sched.next_run_at || null,
        last_run_at: sched.last_run_at || (hcRows[0] && hcRows[0].completed_at) || null,
        last_score: (hcRows[0] && hcRows[0].overall_score != null) ? hcRows[0].overall_score : null,
      },
      remediation: {
        enabled: !!sched.auto_remediate,
        threshold: sched.remediate_threshold || 3,
        // Per-service opt-in list (JSONB array of start op_keys); [] when unset.
        services: Array.isArray(sched.remediate_services) ? sched.remediate_services : [],
      },
      heartbeat_state: heartbeatState.rows,
      recent_activity: recentLog.rows,
      supported_services: HEARTBEAT_SERVICES.map(s => ({ statusOp: s.statusOp, label: s.label, scope: s.scope })),
    });
  } catch (err) {
    console.error('[autopilot/status]', err.message);
    res.status(500).json({ error: 'Failed to load AutoPilot status' });
  }
});

// ── POST save ────────────────────────────────────────────────────────────────
router.post('/api/autopilot/:connectionId/save', requireAuth, requireConnectionOwner, async (req, res) => {
  const connId = parseInt(req.params.connectionId, 10);
  const {
    schedule_enabled, cadence_minutes, alert_email, severity_threshold,
    auto_create_tickets, auto_remediate, remediate_threshold, remediate_services,
  } = req.body;

  // Tier gate — only enabling auto_remediate is gated; turning it off is always
  // allowed, and admins (ADMIN_EMAILS) bypass the tier requirement entirely.
  if (auto_remediate && !isAdmin(req)) {
    const tier = await userTier(req.user.id);
    if (!REMEDIATION_TIERS.includes(tier)) {
      return res.status(402).json({ error: 'Auto-remediation requires Business plan or higher' });
    }
  }

  const VALID_CADENCES = [5, 15, 30, 60, 360, 720, 1440];
  const safeCadence   = VALID_CADENCES.includes(parseInt(cadence_minutes, 10)) ? parseInt(cadence_minutes, 10) : 60;
  const safeThreshold = Math.max(2, Math.min(10, parseInt(remediate_threshold, 10) || 3));

  // remediate_services: JSONB array of start op_keys, filtered to ops the engine
  // can actually fire (RESTARTABLE_OPS) so junk / non-actionable ops (e.g. OACore
  // managed servers) can never be persisted. Deduped. NULL when nothing opted in.
  const safeServices = Array.isArray(remediate_services)
    ? [...new Set(remediate_services.filter(op => RESTARTABLE_OPS.has(op)))]
    : [];
  const servicesJson = safeServices.length ? JSON.stringify(safeServices) : null;

  // Set next_run_at when enabling so getDueSchedules (WHERE next_run_at <= NOW())
  // actually picks the schedule up — omitting it left it NULL and the scheduler
  // never fired. Mirrors db/schedules.js upsertSchedule: NOW()+cadence when
  // enabled, NULL when disabled.
  const nextRunAt = schedule_enabled ? new Date(Date.now() + safeCadence * 60 * 1000) : null;

  try {
    await pool.query(
      `INSERT INTO connection_schedules
         (connection_id, user_id, enabled, cadence_minutes, alert_email,
          severity_threshold, auto_create_tickets, auto_remediate,
          remediate_threshold, remediate_services, next_run_at)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11)
       ON CONFLICT (connection_id) DO UPDATE SET
         enabled = $3, cadence_minutes = $4, alert_email = $5,
         severity_threshold = $6, auto_create_tickets = $7,
         auto_remediate = $8, remediate_threshold = $9, remediate_services = $10::jsonb,
         next_run_at = CASE WHEN $3 THEN $11 ELSE NULL END,
         updated_at = NOW()`,
      [
        connId, req.user.id, !!schedule_enabled, safeCadence,
        alert_email || '', severity_threshold || 'amber',
        auto_create_tickets !== false, !!auto_remediate,
        safeThreshold, servicesJson, nextRunAt,
      ]
    );
    res.json({ ok: true });
  } catch (err) {
    console.error('[autopilot/save]', err.message);
    res.status(500).json({ error: 'Failed to save AutoPilot settings' });
  }
});

// ── GET log (paginated) ──────────────────────────────────────────────────────
router.get('/api/autopilot/:connectionId/log', requireAuth, requireConnectionOwner, async (req, res) => {
  const connId = parseInt(req.params.connectionId, 10);
  const limit  = Math.min(parseInt(req.query.limit, 10) || 50, 200);
  const offset = parseInt(req.query.offset, 10) || 0;
  try {
    const { rows } = await pool.query(
      `SELECT trigger_path, triggered_by, consecutive_count, op_key, op_label,
              outcome, exit_code, stdout_tail, tuneops_ticket_id, executed_at
       FROM auto_remediation_log
       WHERE connection_id = $1
       ORDER BY executed_at DESC LIMIT $2 OFFSET $3`,
      [connId, limit, offset]
    );
    res.json({ log: rows });
  } catch (err) {
    console.error('[autopilot/log]', err.message);
    res.status(500).json({ error: 'Failed to load log' });
  }
});

module.exports = router;
