import { eventBus } from '../lib/eventBus.js';
import { toArray, normalizeWorldFlag } from '../lib/utils.js';
import { contentLoader } from './ContentLoader.js';
import { emailService } from './EmailService.js';
import { questEngine } from './QuestEngine.js';
import { saveState } from './SaveState.js';
import { ticketService } from './TicketService.js';
import { validationEngine } from './ValidationEngine.js';
import { narrativePhaseTracker } from './NarrativePhaseTracker.js';

const DEFAULT_TICK_SECONDS = Number(process.env.INCIDENT_TICK_SECONDS ?? 30);
const ALERT_SENDER = 'Monitoring <alerts@axiomworks.internal>';

export class IncidentScheduler {
  constructor({
    loader = contentLoader,
    email = emailService,
    quests = questEngine,
    save = saveState,
    tickets = ticketService,
    validator = validationEngine,
    phaseTracker = narrativePhaseTracker,
    now = () => Date.now(),
    tickSeconds = DEFAULT_TICK_SECONDS
  } = {}) {
    this.loader = loader;
    this.email = email;
    this.quests = quests;
    this.save = save;
    this.tickets = tickets;
    this.validator = validator;
    this.phaseTracker = phaseTracker;
    this.now = now;
    this.tickSeconds = tickSeconds;
    this._interval = null;
  }

  start() {
    this.stop();
    this.tick().catch((error) => {
      console.error('Incident scheduler tick failed:', error);
    });
    this._interval = setInterval(() => {
      this.tick().catch((error) => {
        console.error('Incident scheduler tick failed:', error);
      });
    }, this.tickSeconds * 1000).unref();
  }

  stop() {
    if (this._interval) {
      clearInterval(this._interval);
      this._interval = null;
    }
  }

  async tick() {
    this._ensureStateContainers();
    await this._processPressureProfiles();
    await this._processGlobalPressureProfiles();
    await this._processRecurringIncidents();
  }

  _ensureStateContainers() {
    const state = this.save.get();
    if (!state.pressure || !state.incidents) {
      this.save.set({
        pressure: state.pressure ?? {},
        incidents: state.incidents ?? {}
      });
    }
  }

  async _processPressureProfiles() {
    const state = this.save.get();
    const trackers = { ...(state.pressure ?? {}) };
    let changed = false;

    for (const [questId, entry] of this.quests.getAllEntries()) {
      if (entry?.state !== 'active') {
        continue;
      }

      const quest = this.loader.get('quests', questId);
      const profileId = quest?.pressure_profile;
      if (!profileId) {
        continue;
      }

      const profile = this.loader.get('pressureProfiles', profileId);
      if (!profile) {
        continue;
      }

      const tracker = trackers[questId] ?? {
        profile_id: profileId,
        started_at: entry.started_at ?? new Date(this.now()).toISOString(),
        fired_step_indexes: []
      };

      const elapsedSeconds = this._elapsedSeconds(tracker.started_at);
      for (const [index, step] of toArray(profile.escalation_steps).entries()) {
        const threshold = Number(step.trigger_after_seconds ?? step.after_seconds ?? -1);
        if (threshold < 0 || tracker.fired_step_indexes.includes(index) || elapsedSeconds < threshold) {
          continue;
        }

        await this._applyPressureStep({
          quest,
          profile,
          questId,
          step,
          stepIndex: index
        });
        tracker.fired_step_indexes = [...tracker.fired_step_indexes, index];
        changed = true;
      }

      trackers[questId] = tracker;
    }

    if (changed) {
      this.save.set({ pressure: trackers });
    }
  }

  async _processGlobalPressureProfiles() {
    if (!(this.loader.pressureProfiles instanceof Map)) return;

    const currentPhase = this.phaseTracker.getPhase();
    const state = this.save.get();
    const trackers = { ...(state.pressure ?? {}) };
    let changed = false;

    for (const profile of this.loader.pressureProfiles.values()) {
      if (!profile.trigger_phase || profile.trigger_phase !== currentPhase) continue;

      const profileId = profile.id;
      const tracker = trackers[profileId] ?? {
        profile_id: profileId,
        started_at: new Date(this.now()).toISOString(),
        fired_step_indexes: []
      };

      const elapsedSeconds = this._elapsedSeconds(tracker.started_at);
      for (const [index, step] of toArray(profile.escalation_steps).entries()) {
        const threshold = Number(step.trigger_after_seconds ?? step.after_seconds ?? -1);
        if (threshold < 0 || tracker.fired_step_indexes.includes(index) || elapsedSeconds < threshold) {
          continue;
        }

        await this._applyGlobalPressureStep({ profile, profileId, step, stepIndex: index });
        tracker.fired_step_indexes = [...tracker.fired_step_indexes, index];
        changed = true;
      }

      trackers[profileId] = tracker;
    }

    if (changed) {
      this.save.set({ pressure: trackers });
    }
  }

  async _processRecurringIncidents() {
    const state = this.save.get();
    const activeIncidents = { ...(state.incidents ?? {}) };
    let changed = false;

    for (const incident of this.loader.incidents.values()) {
      const entry = activeIncidents[incident.id];
      const triggered = this._incidentTriggered(incident, state.world_flags ?? []);

      if (!entry && triggered) {
        activeIncidents[incident.id] = {
          status: 'active',
          started_at: new Date(this.now()).toISOString(),
          fired_step_indexes: []
        };
        changed = true;

        if (incident.notification) {
          this._sendAlert({
            idPrefix: `incident-${incident.id}`,
            subject: incident.title,
            message: incident.notification,
            severity: incident.notification_severity ?? 'warning'
          });
        }
      }

      const nextEntry = activeIncidents[incident.id];
      if (!nextEntry || nextEntry.status !== 'active') {
        continue;
      }

      const elapsedSeconds = this._elapsedSeconds(nextEntry.started_at);
      for (const [index, step] of toArray(incident.escalation_steps).entries()) {
        const threshold = Number(step.trigger_after_seconds ?? step.after_seconds ?? -1);
        if (threshold < 0 || nextEntry.fired_step_indexes.includes(index) || elapsedSeconds < threshold) {
          continue;
        }

        await this._applyIncidentStep({
          incident,
          step,
          stepIndex: index
        });
        nextEntry.fired_step_indexes = [...nextEntry.fired_step_indexes, index];
        changed = true;
      }

      if (await this._incidentResolved(incident)) {
        const resolution = incident.resolution_requirements ?? {};
        activeIncidents[incident.id] = {
          ...nextEntry,
          status: 'resolved',
          resolved_at: new Date(this.now()).toISOString()
        };
        this._applyWorldFlagMutation({
          clearFlag: resolution.clear_flag,
          setFlag: resolution.set_flag
        });
        changed = true;
      }
    }

    if (changed) {
      this.save.set({ incidents: activeIncidents });
    }
  }

  async _applyPressureStep({ quest, profile, questId, step, stepIndex }) {
    if (step.notification) {
      this._sendAlert({
        idPrefix: `pressure-${questId}-${stepIndex}`,
        subject: `${quest.ticket_id ?? questId} escalation`,
        message: step.notification,
        severity: step.notification_severity ?? 'warning'
      });
    }

    if (step.escalate_linked_ticket && quest.ticket_id) {
      this.tickets.setPriority(quest.ticket_id, step.escalate_linked_ticket);
    }
  }

  async _applyGlobalPressureStep({ profile, profileId, step, stepIndex }) {
    if (!step.notification) return;

    const from = step.sender ?? ALERT_SENDER;
    const subject = step.subject ?? profile.label ?? profileId;

    const mail = this.email.send({
      id: `global-pressure-${profileId}-${stepIndex}-${this.now()}`,
      from,
      subject,
      body: step.notification,
      attachments: [],
      replyOptions: []
    });

    eventBus.emit('incident:alert', {
      id: mail.id,
      severity: step.notification_severity ?? 'info',
      message: step.notification,
      subject
    });
  }

  async _applyIncidentStep({ incident, step, stepIndex }) {
    if (step.notification) {
      this._sendAlert({
        idPrefix: `incident-${incident.id}-${stepIndex}`,
        subject: incident.title,
        message: step.notification,
        severity: step.notification_severity ?? 'warning'
      });
    }

    if (Array.isArray(step.world_flags) && step.world_flags.length > 0) {
      this._applyWorldFlagMutation({ setFlags: step.world_flags });
    }

    for (const escalation of toArray(step.escalates_tickets)) {
      if (escalation.ticket_id && escalation.new_priority) {
        this.tickets.setPriority(escalation.ticket_id, escalation.new_priority);
      }
    }

    if (step.action === 'raise_ticket_priority' && step.ticket_id && step.value) {
      this.tickets.setPriority(step.ticket_id, step.value);
    }

    if (step.action === 'trigger_new_ticket' && step.ticket_id) {
      try {
        this.tickets.activateTicket(step.ticket_id);
      } catch {
        // Ignore missing authored recurrence tickets for now.
      }
    }
  }

  async _incidentResolved(incident) {
    const rule = incident.resolution_requirements?.validation;
    if (!rule) {
      return false;
    }

    const result = await this.validator.evaluateRule(rule);
    return result.passed;
  }

  _incidentTriggered(incident, worldFlags) {
    const flags = new Set(worldFlags ?? []);
    const conditions = [
      ...toArray(incident.trigger_flags),
      ...toArray(incident.trigger_conditions)
    ];

    if (conditions.length === 0) {
      return false;
    }

    return conditions.every((condition) => flags.has(normalizeWorldFlag(condition)));
  }

  _applyWorldFlagMutation({ clearFlag = null, setFlag = null, setFlags = [] } = {}) {
    const nextFlags = new Set(this.save.get()?.world_flags ?? []);
    if (clearFlag) {
      nextFlags.delete(clearFlag);
    }

    if (setFlag) {
      nextFlags.add(setFlag);
    }

    for (const flag of setFlags) {
      nextFlags.add(flag);
    }

    this.save.set({ world_flags: [...nextFlags] });
  }

  _sendAlert({ idPrefix, subject, message, severity }) {
    const mail = this.email.send({
      id: `${idPrefix}-${Date.now()}`,
      from: ALERT_SENDER,
      subject: `[${String(severity).toUpperCase()}] ${subject}`,
      body: message,
      attachments: [],
      replyOptions: []
    });

    eventBus.emit('incident:alert', {
      id: mail.id,
      severity,
      message,
      subject
    });
  }

  _elapsedSeconds(startedAt) {
    return Math.max(0, Math.floor((this.now() - new Date(startedAt).getTime()) / 1000));
  }
}

export const incidentScheduler = new IncidentScheduler();
