import type { Redis } from "ioredis";
import BRAND from "../../../../brand.config.json" with { type: "json" };
import { CACHE_KEY } from "./ops-summary.js";

export interface EocPushResult {
  ok: boolean;
  status: number | null;
  error: string | null;
}

const EOC_PUSH_TIMEOUT_MS = 15_000;

function ingestUrl(base: string): string {
  const trimmed = base.trim().replace(/\/+$/, "");
  return `${trimmed}/api/v1/apps/cibello/ingest`;
}

interface EocFeedbackPost {
  typ?: string;
  rubrik?: string;
  status?: string;
  created_at?: string;
  skapad?: string;
  [key: string]: unknown;
}

interface EocFeedback {
  oppna?: number;
  senaste?: EocFeedbackPost[];
  [key: string]: unknown;
}

interface EocTile {
  label: string;
  value: string;
  tone?: string;
  [key: string]: unknown;
}

interface EocBoard {
  title: string;
  tiles: EocTile[];
  [key: string]: unknown;
}

interface EocWall {
  boards: EocBoard[];
  [key: string]: unknown;
}

interface EocQueue {
  workers_ok?: boolean;
  [key: string]: unknown;
}

interface EocSafety {
  allergen_invariant_brott?: number;
  [key: string]: unknown;
}

/**
 * Build a payload that matches EOC's authoritative contract:
 * - feedback.senaste[].created_at must be sent as feedback.senaste[].skapad.
 * - wall.boards[].title interpolates {brand} with the brand name.
 * - wall tile tones use EOC vocabulary (good|warn|crit|"").
 * - Workers and Allergen-brott tiles can escalate to "crit" from flat fields.
 * All other keys are forwarded unchanged.
 */
function mapPayloadForEoc(parsed: Record<string, unknown>): Record<string, unknown> {
  const mapped: Record<string, unknown> = { ...parsed };
  const feedback = mapped.feedback as EocFeedback | undefined;
  if (feedback && Array.isArray(feedback.senaste)) {
    mapped.feedback = {
      ...feedback,
      senaste: feedback.senaste.map((post) => {
        const { created_at, ...rest } = post;
        return {
          ...rest,
          ...(created_at !== undefined ? { skapad: created_at } : {}),
        };
      }),
    };
  }

  const wall = mapped.wall as EocWall | undefined;
  if (wall && Array.isArray(wall.boards)) {
    const jobb = mapped.jobb as EocQueue | undefined;
    const sakerhet = mapped.sakerhet as EocSafety | undefined;
    const workersDown = jobb?.workers_ok === false;
    const allergenBreach = (sakerhet?.allergen_invariant_brott ?? 0) > 0;

    mapped.wall = {
      ...wall,
      boards: wall.boards.map((board) => ({
        ...board,
        title: board.title.replace(/\{brand\}/g, BRAND.name),
        tiles: board.tiles.map((tile) => {
          let tone = tile.tone === "ok" ? "good" : tile.tone;
          if (tile.label === "Workers" && workersDown) {
            tone = "crit";
          }
          if (tile.label === "Allergen-brott" && allergenBreach) {
            tone = "crit";
          }
          if (tile.label === "AI-budget" && tone === "crit") {
            // already crit from buildWall
          }
          return { ...tile, tone };
        }),
      })),
    };
  }

  return mapped;
}

/**
 * Push the cached ops summary to EOC. Never throws; failures are returned
 * as `ok: false` and must be logged by the caller.
 */
export async function pushOpsSummaryToEoc(redis: Redis): Promise<EocPushResult> {
  const eocUrl = process.env.EOC_URL?.trim();
  const token = process.env.EOC_PUSH_TOKEN?.trim();

  if (!eocUrl || !token) {
    return { ok: false, status: null, error: "EOC_URL or EOC_PUSH_TOKEN not configured" };
  }

  const json = await redis.get(CACHE_KEY);
  if (!json) {
    return { ok: false, status: null, error: "ops summary cache miss" };
  }

  // Guard against accidentally sending non-object payloads.
  let parsed: unknown;
  try {
    parsed = JSON.parse(json);
  } catch {
    return { ok: false, status: null, error: "ops summary cache contains invalid JSON" };
  }
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
    return { ok: false, status: null, error: "ops summary cache is not a JSON object" };
  }

  let body: string;
  try {
    body = JSON.stringify(mapPayloadForEoc(parsed as Record<string, unknown>));
  } catch (err) {
    return {
      ok: false,
      status: null,
      error: `payload mapping failed: ${err instanceof Error ? err.message : String(err)}`,
    };
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), EOC_PUSH_TIMEOUT_MS);

  try {
    const res = await fetch(ingestUrl(eocUrl), {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body,
      signal: controller.signal,
    });
    clearTimeout(timeout);

    if (res.status === 204) {
      return { ok: true, status: 204, error: null };
    }

    const text = await res.text().catch(() => "<unreadable body>");
    return { ok: false, status: res.status, error: text.slice(0, 500) };
  } catch (err) {
    clearTimeout(timeout);
    const message = err instanceof Error ? err.message : String(err);
    return { ok: false, status: null, error: message };
  }
}
