import { useEffect, useMemo, useState } from 'react';
import type { AgentEvent, AgentView, LogEntry } from '@stream-control/shared';
import { api } from '../api';
import { formatTime } from '../format';

interface Props {
  agent: AgentView;
  /** Entrées reçues en direct depuis l'ouverture du dashboard, toutes VM confondues. */
  liveLogs: LogEntry[];
  onClose: () => void;
}

interface EventStyle {
  icon: string;
  tone: 'ok' | 'warn' | 'rec' | 'offline' | 'error' | 'info';
}

/**
 * Pictogramme et couleur par type d'évènement.
 *
 * On s'en tient aux glyphes typographiques déjà employés sur les fiches (⏺ ⏸ ⛶ ⚙)
 * plutôt qu'à des émojis : même graisse, même alignement, lisible à 12 px.
 */
const EVENT_STYLES: Record<AgentEvent, EventStyle> = {
  'agent.connected': { icon: '⇢', tone: 'ok' },
  'agent.disconnected': { icon: '⇠', tone: 'offline' },
  'agent.enrolled': { icon: '+', tone: 'ok' },
  'agent.updated': { icon: '⬆', tone: 'ok' },
  'obs.connected': { icon: '◉', tone: 'ok' },
  'obs.disconnected': { icon: '◌', tone: 'warn' },
  'record.started': { icon: '⏺', tone: 'rec' },
  'record.stopped': { icon: '■', tone: 'offline' },
  'record.paused': { icon: '⏸', tone: 'warn' },
  'record.resumed': { icon: '▶', tone: 'ok' },
  'record.split': { icon: '✂', tone: 'info' },
  'stream.started': { icon: '⇡', tone: 'rec' },
  'stream.stopped': { icon: '⇣', tone: 'offline' },
  'capture.started': { icon: '▣', tone: 'rec' },
  'capture.stopped': { icon: '□', tone: 'offline' },
  'watch.started': { icon: '⟳', tone: 'info' },
  'watch.private': { icon: '⊘', tone: 'warn' },
  'watch.public': { icon: '◉', tone: 'ok' },
  'watch.offline': { icon: '○', tone: 'offline' },
  'watch.failed': { icon: '!', tone: 'warn' },
  'fullscreen.restored': { icon: '⛶', tone: 'ok' },
  'fullscreen.failed': { icon: '⛶', tone: 'warn' },
  'browser.opened': { icon: '⊞', tone: 'info' },
  'browser.closed': { icon: '⊟', tone: 'offline' },
  'browser.imported': { icon: '⇄', tone: 'ok' },
  'browser.importFailed': { icon: '⇄', tone: 'warn' },
  'preset.applied': { icon: '≡', tone: 'ok' },
  'preset.failed': { icon: '≡', tone: 'warn' },
  'config.changed': { icon: '⚙', tone: 'info' },
  'command.failed': { icon: '✖', tone: 'error' },
};

const FILTERS = [
  { id: 'all', label: 'Tout' },
  { id: 'record', label: 'Enregistrement' },
  { id: 'obs', label: 'OBS' },
  { id: 'watch', label: 'Surveillance' },
  { id: 'issues', label: 'Problèmes' },
] as const;

type FilterId = (typeof FILTERS)[number]['id'];

/** Le préfixe de l'évènement porte la catégorie ; les entrées sans évènement restent dans « Tout ». */
const FILTER_PREFIXES: Record<Exclude<FilterId, 'all' | 'issues'>, string[]> = {
  record: ['record.', 'capture.', 'stream.'],
  obs: ['obs.', 'preset.', 'config.'],
  watch: ['watch.', 'fullscreen.', 'browser.'],
};

function matches(entry: LogEntry, filter: FilterId): boolean {
  if (filter === 'all') return true;
  if (filter === 'issues') return entry.level === 'warn' || entry.level === 'error';
  return FILTER_PREFIXES[filter].some((prefix) => entry.event?.startsWith(prefix));
}

export function AgentHistory({ agent, liveLogs, onClose }: Props) {
  const [fetched, setFetched] = useState<LogEntry[] | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [filter, setFilter] = useState<FilterId>('all');

  useEffect(() => {
    let cancelled = false;
    setFetched(null);
    setError(null);
    api
      .agentLogs(agent.id)
      .then((result) => {
        if (!cancelled) setFetched(result.logs);
      })
      .catch((err: unknown) => {
        if (!cancelled) setError(err instanceof Error ? err.message : 'Historique indisponible');
      });
    return () => {
      cancelled = true;
    };
  }, [agent.id]);

  /**
   * L'historique persistant et le flux temps réel se recouvrent : les entrées
   * arrivées pendant le chargement figurent dans les deux. L'identifiant de ligne
   * tranche, et l'ordre décroissant place le plus récent en tête.
   */
  const entries = useMemo(() => {
    const byId = new Map<number, LogEntry>();
    for (const entry of fetched ?? []) byId.set(entry.id, entry);
    for (const entry of liveLogs) {
      if (entry.agentId === agent.id) byId.set(entry.id, entry);
    }
    return [...byId.values()].sort((a, b) => b.id - a.id);
  }, [agent.id, fetched, liveLogs]);

  const visible = entries.filter((entry) => matches(entry, filter));
  const days = groupByDay(visible);

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal modal-tall" onClick={(event) => event.stopPropagation()}>
        <header className="modal-head">
          <h2>Historique · {agent.name}</h2>
          <button className="icon" onClick={onClose}>
            ✕
          </button>
        </header>

        <div className="history-filters">
          {FILTERS.map((entry) => (
            <button
              key={entry.id}
              className={filter === entry.id ? 'chip active' : 'chip'}
              onClick={() => setFilter(entry.id)}
            >
              {entry.label}
            </button>
          ))}
          <div className="spacer" />
          <span className="muted small">{visible.length} évènement(s)</span>
        </div>

        <div className="modal-body history-body">
          {error && <p className="error small">{error}</p>}
          {!error && fetched === null && <p className="muted small">Chargement…</p>}
          {!error && fetched !== null && visible.length === 0 && (
            <p className="muted small">
              {entries.length === 0
                ? "Aucun évènement pour cette VM. L'historique se remplit dès que l'agent se connecte."
                : 'Aucun évènement pour ce filtre.'}
            </p>
          )}

          {days.map(([day, dayEntries]) => (
            <section key={day} className="history-day">
              <h3 className="history-day-label">{day}</h3>
              {dayEntries.map((entry) => {
                const style = entry.event ? EVENT_STYLES[entry.event] : null;
                return (
                  <div key={entry.id} className={`history-line level-${entry.level}`}>
                    <span className="mono small muted">{formatTime(entry.ts)}</span>
                    <span
                      className={`history-icon ${style?.tone ?? 'info'}`}
                      title={entry.event ?? 'évènement non typé'}
                    >
                      {style?.icon ?? '·'}
                    </span>
                    <span className="history-message">{entry.message}</span>
                  </div>
                );
              })}
            </section>
          ))}
        </div>

        <footer className="modal-foot">
          <span className="muted small">
            Les entrées les plus anciennes sont purgées avec le journal global.
          </span>
          <div className="spacer" />
          <button className="ghost" onClick={onClose}>
            Fermer
          </button>
        </footer>
      </div>
    </div>
  );
}

/** Regroupe par jour en conservant l'ordre décroissant reçu. */
function groupByDay(entries: LogEntry[]): Array<[string, LogEntry[]]> {
  const groups = new Map<string, LogEntry[]>();
  for (const entry of entries) {
    const day = dayLabel(entry.ts);
    const bucket = groups.get(day);
    if (bucket) bucket.push(entry);
    else groups.set(day, [entry]);
  }
  return [...groups.entries()];
}

function dayLabel(ts: number): string {
  const date = new Date(ts);
  const today = new Date();
  const yesterday = new Date(today.getTime() - 86_400_000);

  if (isSameDay(date, today)) return "Aujourd'hui";
  if (isSameDay(date, yesterday)) return 'Hier';
  return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' });
}

function isSameDay(a: Date, b: Date): boolean {
  return (
    a.getFullYear() === b.getFullYear() &&
    a.getMonth() === b.getMonth() &&
    a.getDate() === b.getDate()
  );
}
