/**
 * External Status Check Service
 *
 * Fetches document status from external authority portals, detects changes,
 * updates entries, and sends instant email notifications.
 */

import type { Prisma } from "@prisma/client";
import type {
  ExternalStatusRow,
  ExternalStatusChange,
  ExternalStatusTracking,
  ExternalDocStatus,
} from "../types";
import type { RegistryEntry } from "../types";
import { getAuthority } from "./authority-catalog";
import type { AuthorityConfig } from "./authority-catalog";

// ── Hash helper ──

/** Simple deterministic hash for change detection */
export function computeStatusHash(row: ExternalStatusRow): string {
  const str = [
    row.dataVenire,
    row.oraVenire,
    row.sursa,
    row.dataTrimitere,
    row.oraTrimitere,
    row.destinatie,
    row.modRezolvare,
    row.comentarii,
  ].join("|");

  // djb2 hash
  let hash = 5381;
  for (let i = 0; i < str.length; i++) {
    hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
  }
  return Math.abs(hash).toString(36);
}

// ── Single entry check ──

export interface StatusCheckResult {
  entryId: string;
  entryNumber: string;
  changed: boolean;
  newRow: ExternalStatusRow | null;
  newStatus: ExternalDocStatus;
  error: string | null;
}

/** Check a single entry's status at its authority portal */
export async function checkExternalStatus(
  entry: RegistryEntry,
  authority: AuthorityConfig,
): Promise<StatusCheckResult> {
  const tracking = entry.externalStatusTracking;
  if (!tracking) {
    return {
      entryId: entry.id,
      entryNumber: entry.number,
      changed: false,
      newRow: null,
      newStatus: "necunoscut",
      error: "No tracking configured",
    };
  }

  try {
    const payload = authority.buildPayload({
      regNumber: tracking.regNumber,
      petitionerName: tracking.petitionerName,
      regDate: tracking.regDate,
    });

    const res = await fetch(authority.endpoint, {
      method: "POST",
      headers: {
        "Content-Type": authority.contentType,
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
      },
      body: payload,
      signal: AbortSignal.timeout(15_000),
    });

    if (!res.ok) {
      return {
        entryId: entry.id,
        entryNumber: entry.number,
        changed: false,
        newRow: null,
        newStatus: tracking.semanticStatus,
        error: `HTTP ${res.status}: ${res.statusText}`,
      };
    }

    const html = await res.text();
    const rows = authority.parseResponse(html);

    if (rows.length === 0) {
      return {
        entryId: entry.id,
        entryNumber: entry.number,
        changed: false,
        newRow: null,
        newStatus: tracking.semanticStatus,
        error: "Nu s-au gasit date de status in raspunsul autoritatii",
      };
    }

    // Take the last row (most recent status)
    const lastRow = rows[rows.length - 1]!;
    const newHash = computeStatusHash(lastRow);
    const newStatus = authority.deriveStatus(lastRow);
    const changed = newHash !== tracking.statusHash;

    return {
      entryId: entry.id,
      entryNumber: entry.number,
      changed,
      newRow: lastRow,
      newStatus,
      error: null,
    };
  } catch (err) {
    const message = err instanceof Error ? err.message : "Unknown error";
    return {
      entryId: entry.id,
      entryNumber: entry.number,
      changed: false,
      newRow: null,
      newStatus: tracking.semanticStatus,
      error: message,
    };
  }
}

// ── Batch orchestrator ──

export interface StatusCheckRunResult {
  success: boolean;
  checked: number;
  changed: number;
  emailsSent: number;
  errors: string[];
}

/** Run status checks for all active entries */
export async function runStatusCheck(
  dryRun = false,
): Promise<StatusCheckRunResult> {
  const { prisma } = await import("@/core/storage/prisma");

  const result: StatusCheckRunResult = {
    success: true,
    checked: 0,
    changed: 0,
    emailsSent: 0,
    errors: [],
  };

  // 1. Load all entries
  const rows = await prisma.keyValueStore.findMany({
    where: { namespace: "registratura" },
    select: { key: true, value: true },
  });

  const entries = rows
    .filter((r) => r.key.startsWith("entry:"))
    .map((r) => r.value as unknown as RegistryEntry)
    .filter((e) => e.externalStatusTracking?.active);

  if (entries.length === 0) {
    return { ...result, errors: ["Nu exista entries cu monitorizare activa"] };
  }

  // 2. Group by authority for rate limiting
  const byAuthority = new Map<string, RegistryEntry[]>();
  for (const entry of entries) {
    const authId = entry.externalStatusTracking!.authorityId;
    const group = byAuthority.get(authId) ?? [];
    group.push(entry);
    byAuthority.set(authId, group);
  }

  // 3. Load notification subscribers
  const { getAllPreferences } = await import(
    "@/core/notifications/notification-service"
  );
  const preferences = await getAllPreferences();
  const statusSubscribers = preferences.filter(
    (p) =>
      !p.globalOptOut &&
      p.enabledTypes.includes("status-change" as import("@/core/notifications/types").NotificationType),
  );

  // 4. Check each authority group
  for (const [authorityId, groupEntries] of byAuthority.entries()) {
    const authority = getAuthority(authorityId);
    if (!authority) {
      result.errors.push(`Autoritate necunoscuta: ${authorityId}`);
      continue;
    }

    for (const entry of groupEntries) {
      result.checked++;

      const checkResult = await checkExternalStatus(entry, authority);

      // Update tracking state
      const tracking = entry.externalStatusTracking!;
      tracking.lastCheckAt = new Date().toISOString();
      tracking.lastError = checkResult.error;

      if (checkResult.changed && checkResult.newRow) {
        result.changed++;
        const newHash = computeStatusHash(checkResult.newRow);

        const change: ExternalStatusChange = {
          timestamp: new Date().toISOString(),
          row: checkResult.newRow,
          semanticStatus: checkResult.newStatus,
          notified: false,
        };

        tracking.lastStatusRow = checkResult.newRow;
        tracking.statusHash = newHash;
        tracking.semanticStatus = checkResult.newStatus;

        // Auto-deactivate monitoring when resolved or rejected
        if (
          checkResult.newStatus === "solutionat" ||
          checkResult.newStatus === "respins"
        ) {
          tracking.active = false;
        }

        // Cap history at 50
        tracking.history.push(change);
        if (tracking.history.length > 50) {
          tracking.history = tracking.history.slice(-50);
        }

        if (!dryRun) {
          // Save updated entry
          await prisma.keyValueStore.update({
            where: {
              namespace_key: {
                namespace: "registratura",
                key: `entry:${entry.id}`,
              },
            },
            data: {
              value: entry as unknown as Prisma.InputJsonValue,
            },
          });

          // Send instant email notification
          const emailCount = await sendStatusChangeEmails(
            entry,
            change,
            authority,
            statusSubscribers,
          );
          result.emailsSent += emailCount;
          change.notified = emailCount > 0;
        }
      } else if (!dryRun && checkResult.error !== tracking.lastError) {
        // Save error state update even if no status change
        await prisma.keyValueStore.update({
          where: {
            namespace_key: {
              namespace: "registratura",
              key: `entry:${entry.id}`,
            },
          },
          data: {
            value: entry as unknown as Prisma.InputJsonValue,
          },
        });
      }

      // Rate limit: 500ms between requests to same authority
      if (groupEntries.indexOf(entry) < groupEntries.length - 1) {
        await new Promise((r) => setTimeout(r, 500));
      }
    }
  }

  return result;
}

// ── Email notification ──

async function sendStatusChangeEmails(
  entry: RegistryEntry,
  change: ExternalStatusChange,
  authority: AuthorityConfig,
  subscribers: Array<{ email: string; name: string; company: import("@/core/auth/types").CompanyId }>,
): Promise<number> {
  const { sendEmail } = await import("@/core/notifications/email-service");
  const { EXTERNAL_STATUS_LABELS } = await import("../types");

  // Filter subscribers to same company as entry
  const relevant = subscribers.filter((s) => s.company === entry.company);
  let sent = 0;

  const statusLabel =
    EXTERNAL_STATUS_LABELS[change.semanticStatus] ?? change.semanticStatus;
  const row = change.row;

  const subject = `Status actualizat: ${entry.number} — ${statusLabel}`;
  const html = `
    <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 600px;">
      <h2 style="color: #1a1a1a; margin-bottom: 4px;">Actualizare status extern</h2>
      <p style="color: #666; margin-top: 0;">Detectat la ${new Date(change.timestamp).toLocaleString("ro-RO")}</p>

      <table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
        <tr>
          <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #f5f5f5;">Nr. registru</td>
          <td style="padding: 8px; border: 1px solid #ddd;">${entry.number}</td>
        </tr>
        <tr>
          <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #f5f5f5;">Subiect</td>
          <td style="padding: 8px; border: 1px solid #ddd;">${entry.subject}</td>
        </tr>
        <tr>
          <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #f5f5f5;">Autoritate</td>
          <td style="padding: 8px; border: 1px solid #ddd;">${authority.name}</td>
        </tr>
        <tr>
          <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #f5f5f5;">Nr. inregistrare</td>
          <td style="padding: 8px; border: 1px solid #ddd;">${entry.externalStatusTracking?.regNumber ?? "—"}</td>
        </tr>
      </table>

      <h3 style="color: #1a1a1a; margin-bottom: 8px;">Ultimul status</h3>
      <table style="width: 100%; border-collapse: collapse; margin: 8px 0;">
        <tr style="background: #f5f5f5;">
          <th style="padding: 6px 8px; border: 1px solid #ddd; text-align: left; font-size: 12px;">Sursa</th>
          <th style="padding: 6px 8px; border: 1px solid #ddd; text-align: left; font-size: 12px;">Destinatie</th>
          <th style="padding: 6px 8px; border: 1px solid #ddd; text-align: left; font-size: 12px;">Mod rezolvare</th>
          <th style="padding: 6px 8px; border: 1px solid #ddd; text-align: left; font-size: 12px;">Data</th>
        </tr>
        <tr>
          <td style="padding: 6px 8px; border: 1px solid #ddd;">${row.sursa}</td>
          <td style="padding: 6px 8px; border: 1px solid #ddd;">${row.destinatie}</td>
          <td style="padding: 6px 8px; border: 1px solid #ddd; font-weight: bold;">${row.modRezolvare || "—"}</td>
          <td style="padding: 6px 8px; border: 1px solid #ddd;">${row.dataVenire} ${row.oraVenire}</td>
        </tr>
      </table>

      ${row.comentarii ? `<p style="color: #666; font-size: 13px;"><strong>Comentarii:</strong> ${row.comentarii}</p>` : ""}

      <p style="color: #999; font-size: 11px; margin-top: 24px; border-top: 1px solid #eee; padding-top: 12px;">
        Alerte Termene &mdash; ArchiTools
      </p>
    </div>
  `;

  for (const subscriber of relevant) {
    try {
      await sendEmail({ to: subscriber.email, subject, html });
      sent++;
    } catch (err) {
      // Don't fail entire job on single email failure
      console.error(
        `Failed to send status email to ${subscriber.email}:`,
        err,
      );
    }
  }

  return sent;
}

// ── Single entry check (for UI-triggered manual check) ──

export async function checkSingleEntry(
  entryId: string,
): Promise<StatusCheckResult & { tracking?: ExternalStatusTracking }> {
  const { prisma } = await import("@/core/storage/prisma");

  const row = await prisma.keyValueStore.findUnique({
    where: {
      namespace_key: { namespace: "registratura", key: `entry:${entryId}` },
    },
  });

  if (!row) {
    return {
      entryId,
      entryNumber: "",
      changed: false,
      newRow: null,
      newStatus: "necunoscut",
      error: "Entry not found",
    };
  }

  const entry = row.value as unknown as RegistryEntry;
  const tracking = entry.externalStatusTracking;

  if (!tracking?.active) {
    return {
      entryId,
      entryNumber: entry.number,
      changed: false,
      newRow: null,
      newStatus: "necunoscut",
      error: "Monitorizarea nu este activa",
    };
  }

  const authority = getAuthority(tracking.authorityId);
  if (!authority) {
    return {
      entryId,
      entryNumber: entry.number,
      changed: false,
      newRow: null,
      newStatus: tracking.semanticStatus,
      error: `Autoritate necunoscuta: ${tracking.authorityId}`,
    };
  }

  const result = await checkExternalStatus(entry, authority);

  // Update tracking state
  tracking.lastCheckAt = new Date().toISOString();
  tracking.lastError = result.error;

  if (result.changed && result.newRow) {
    const newHash = computeStatusHash(result.newRow);
    const change: ExternalStatusChange = {
      timestamp: new Date().toISOString(),
      row: result.newRow,
      semanticStatus: result.newStatus,
      notified: false,
    };

    tracking.lastStatusRow = result.newRow;
    tracking.statusHash = newHash;
    tracking.semanticStatus = result.newStatus;

    // Auto-deactivate monitoring when resolved or rejected
    if (
      result.newStatus === "solutionat" ||
      result.newStatus === "respins"
    ) {
      tracking.active = false;
    }

    tracking.history.push(change);
    if (tracking.history.length > 50) {
      tracking.history = tracking.history.slice(-50);
    }
  }

  // Save updated entry
  await prisma.keyValueStore.update({
    where: {
      namespace_key: { namespace: "registratura", key: `entry:${entryId}` },
    },
    data: { value: entry as unknown as Prisma.InputJsonValue },
  });

  return { ...result, tracking };
}
