<?php

namespace App\Services;

use App\Events\IncidentOpened;
use App\Models\AlertRule;
use App\Models\Incident;
use App\Models\IncidentUpdate;
use App\Services\RootCauseAnalyzer;
use App\Services\WarRoomService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

/**
 * Lightweight incident lifecycle manager.
 *
 * Rules are matched against subjects (servers, websites, ports, certs).
 * For each match, we either open a new incident (if none active) or update
 * the existing one. When the trigger condition clears we auto-resolve if
 * the rule allows it.
 */
class IncidentManagerService
{
    public function evaluate(Model $subject, string $trigger, string $severity = 'medium', ?string $detector = null, array $meta = []): ?Incident
    {
        // De-duplicate: only one open incident per (subject, detector).
        $existing = Incident::where('subject_type', get_class($subject))
            ->where('subject_id', $subject->id)
            ->where('detector', $detector)
            ->whereIn('status', ['open', 'investigating', 'identified', 'monitoring'])
            ->first();

        if ($existing) {
            return $this->appendUpdate($existing, $trigger, $meta);
        }

        return $this->openIncident($subject, $trigger, $severity, $detector, $meta);
    }

    /**
     * Friendly alias used by detector services. Behaves the same as evaluate()
     * but with a slightly clearer name for code that always wants to either
     * create or append.
     */
    public function openOrUpdate(Model $subject, string $title, string $severity = 'medium', ?string $detector = null, array $meta = []): ?Incident
    {
        return $this->evaluate($subject, $title, $severity, $detector, $meta);
    }

    public function resolveByDetector(Model $subject, string $detector): void
    {
        $this->autoResolve($subject, $detector);
    }


    public function autoResolve(Model $subject, string $detector): void
    {
        Incident::where('subject_type', get_class($subject))
            ->where('subject_id', $subject->id)
            ->where('detector', $detector)
            ->whereIn('status', ['open', 'investigating', 'identified', 'monitoring'])
            ->each(function (Incident $inc) {
                $inc->update([
                    'status' => 'resolved',
                    'resolved_at' => now(),
                    'mttr_seconds' => $inc->detected_at->diffInSeconds(now()),
                ]);
                IncidentUpdate::create([
                    'incident_id' => $inc->id,
                    'type' => 'resolved',
                    'message' => 'Auto-resolved: condition cleared.',
                ]);

                // Close any war room that was opened for this incident.
                try {
                    app(WarRoomService::class)->closeForIncident($inc);
                } catch (\Throwable $e) {
                    Log::warning("War room close failed: " . $e->getMessage());
                }

                // Recovery notification — same channels that fired the alert
                // get the "all clear" message. Looks up the original rule
                // (if any) via metadata.rule_id stored at open time.
                $ruleId = $inc->metadata['rule_id'] ?? null;
                $channels = $inc->metadata['channels'] ?? [];
                if (!empty($channels)) {
                    app(\App\Services\NotificationDispatcher::class)
                        ->dispatchForIncident($inc, $channels, $ruleId, 'resolved');
                }

                // Notify the AI Portal so it can mark the mirrored issue
                // as fixed and tear down any pending fix proposals that
                // are now obsolete.
                try {
                    app(AiPortalNotifier::class)->dispatch($inc, 'incident.resolved');
                } catch (\Throwable $e) {
                    Log::warning("AI Portal resolve notify failed: " . $e->getMessage());
                }
            });
    }

    protected function openIncident(Model $subject, string $title, string $severity, ?string $detector, array $meta): Incident
    {
        $incident = Incident::create([
            'public_id' => Incident::generatePublicId(),
            'title' => $title,
            'severity' => $severity,
            'status' => 'open',
            'source' => 'auto',
            'detector' => $detector,
            'subject_type' => get_class($subject),
            'subject_id' => $subject->id,
            'customer_impact' => in_array($severity, ['critical', 'high'], true),
            'detected_at' => now(),
            'metadata' => $meta,
        ]);

        IncidentUpdate::create([
            'incident_id' => $incident->id,
            'type' => 'detected',
            'message' => $title,
        ]);

        // Enrich the incident with diagnostic context. These are best-effort —
        // failures here must not prevent the incident from opening or alerting,
        // so each step is wrapped and logged.

        // 1) Auto-link runbook by detector key. Operators see the playbook
        //    inline instead of hunting through wikis.
        try {
            if ($detector && Schema::hasTable('runbooks')) {
                $runbook = \App\Models\Runbook::where('detector_key', $detector)->first();
                if ($runbook) {
                    $incident->update(['runbook_id' => $runbook->id]);
                    $runbook->increment('used_count');
                }
            }
        } catch (\Throwable $e) {
            Log::warning("Runbook auto-link failed: " . $e->getMessage());
        }

        // 2) Snapshot recent change events. We want a frozen list of "what
        //    happened on this server in the hour before the incident" — even
        //    if change_events get cleaned up later.
        try {
            if (Schema::hasColumn('incidents', 'change_events_at_open') && $subject instanceof \App\Models\Server) {
                $changes = \App\Models\ChangeEvent::where('server_id', $subject->id)
                    ->where('occurred_at', '>=', now()->subHour())
                    ->orderByDesc('occurred_at')
                    ->limit(20)
                    ->get(['category', 'summary', 'occurred_at', 'details'])
                    ->all();
                $incident->update(['change_events_at_open' => $changes]);
            }
        } catch (\Throwable $e) {
            Log::warning("Change event snapshot failed: " . $e->getMessage());
        }

        // 3) Root-cause hint generation. Looks at change events + dependency
        //    health + recent incidents to give the operator a head start.
        try {
            app(RootCauseAnalyzer::class)->attachHints($incident);
        } catch (\Throwable $e) {
            Log::warning("Root cause analysis failed: " . $e->getMessage());
        }

        // 4) Open a Telegram war room for critical incidents. Auto-archives
        //    on resolve. No-op for lower severities.
        try {
            if ($severity === 'critical') {
                app(WarRoomService::class)->openForIncident($incident);
            }
        } catch (\Throwable $e) {
            Log::warning("War room open failed: " . $e->getMessage());
        }

        // 5) Notify the HostSeba AI Admin Portal so it can mirror this
        //    incident as a fixable issue and queue an AI fix proposal.
        //    Best-effort — failure here must not block incident creation;
        //    the AI Portal also polls /api/incidents as a backstop.
        try {
            app(AiPortalNotifier::class)->dispatch($incident, 'incident.opened');
        } catch (\Throwable $e) {
            Log::warning("AI Portal notify failed: " . $e->getMessage());
        }

        event(new IncidentOpened($incident));
        return $incident;
    }

    protected function appendUpdate(Incident $incident, string $trigger, array $meta): Incident
    {
        IncidentUpdate::create([
            'incident_id' => $incident->id,
            'type' => 'update',
            'message' => $trigger,
            'changes' => $meta,
        ]);
        return $incident;
    }
}
