import { describe, expect, it, vi } from "vitest";
import type { CanvasState } from "@dada/shared-contracts";

import {
  COLOR_CARD_SOURCE_GEOMETRY,
  P0A_COLOR_CARDS,
  createColorCardElement,
  extractMmcqPalette,
  refreshColorCards,
} from "../../apps/web/src/palette-provider.js";
import {
  DYN012_RENDER_LAYOUT,
  LocationConsentGate,
  P0A_DYNAMIC_STICKERS,
  createDynamicStickerElement,
  normalizeSocialId,
  overrideDynamicStickerValue,
} from "../../apps/web/src/dynamic-provider.js";
import { DYNAMIC_RENDER_MODELS, DYNAMIC_RESOURCE_VERSION } from "../../apps/web/src/dynamic-render-models.js";

const identity = { createdAt: "2026-08-03T04:00:00.000Z", elementId: "00000000-0000-4000-8000-000000000801" };

function canvas(elements: CanvasState["elements"] = []): CanvasState {
  return {
    background: { adjustments: { brightness: 0, contrast: 0, crop: null, filter: "none", fit: "fill", saturation: 0, sharpness: 0, temperature: 0 }, asset_id: "00000000-0000-4000-8000-000000000802" },
    elements, pixel_height: 1440, pixel_width: 1080, ratio: "3:4", schema_version: 1,
  };
}

function colorPixels(colors: Array<{ count: number; rgb: [number, number, number] }>) {
  const values: number[] = [];
  for (const color of colors) {
    for (let index = 0; index < color.count; index += 1) values.push(...color.rgb, 255);
  }
  return new Uint8ClampedArray(values);
}

describe("TASK-WP4-04 deterministic color cards", () => {
  it("keeps the archived 320px color-card geometry instead of enlarged approximations", () => {
    expect(COLOR_CARD_SOURCE_GEOMETRY).toMatchObject({
      style_01: { bounds: { bottom: 76, left: -26, right: 26, top: -74 }, palette: { bottom: 37, left: -23, right: 23, top: -71 } },
      style_02: { bounds: { bottom: 69, left: -18, right: 18, top: -77 } },
      style_08: { bounds: { bottom: 10, left: -73, right: 73, top: -9 } },
      style_16: { bounds: { bottom: 9, left: -78, right: 78, top: -9 } },
    });
  });

  it("uses the four-item P0-A allowlist and produces a stable five-color MMCQ palette", () => {
    expect(P0A_COLOR_CARDS.map((item) => item.cardId)).toEqual(["COLOR001", "COLOR002", "COLOR008", "COLOR016"]);
    const pixels = colorPixels([
      { count: 50, rgb: [244, 32, 32] }, { count: 40, rgb: [32, 210, 96] }, { count: 30, rgb: [24, 96, 220] },
      { count: 20, rgb: [248, 210, 48] }, { count: 10, rgb: [120, 64, 180] },
    ]);
    const first = extractMmcqPalette(pixels);
    const second = extractMmcqPalette(pixels);
    expect(first).toEqual(second);
    expect(first).toHaveLength(5);
    expect(first).toEqual([...first].sort((left, right) => right.population - left.population || left.rgbValue - right.rgbValue));
    expect(first.every((entry) => /^#[0-9A-F]{6}$/.test(entry.hex))).toBe(true);
  });

  it("snapshots five colors and only refreshes them when the raw background changes", () => {
    const initialPalette = ["#F42020", "#20D260", "#1860DC", "#F8D230", "#7840B4"];
    const replacement = ["#111111", "#333333", "#555555", "#777777", "#999999"];
    const element = createColorCardElement(P0A_COLOR_CARDS[0]!, initialPalette, identity, 0);
    const state = canvas([element]);
    expect(element.style_id).toBe("style_01");
    expect(element.style_parameters?.palette_algorithm_version).toBe("mmcq-v1");
    expect(element.colors).toEqual(initialPalette);
    expect(refreshColorCards(state, replacement).elements[0]?.colors).toEqual(replacement);
    expect(state.elements[0]?.colors).toEqual(initialPalette);
  });
});

describe("TASK-WP4-04 dynamic providers", () => {
  const context = {
    now: new Date("2026-08-03T09:07:00+08:00"),
    profile: { creatorName: "Dada Creator", socialId: "@@dada" },
  };

  it("exposes exactly ten P0-A providers and snapshots time and identity", () => {
    expect(P0A_DYNAMIC_STICKERS.map((item) => item.templateId)).toEqual([
      "DYN001", "DYN002", "DYN003", "DYN004", "DYN007", "DYN008", "DYN011", "DYN012", "DYN015", "DYN016",
    ]);
    const time = createDynamicStickerElement("DYN012", context, identity, 0);
    expect(time.formatted_value).toBe("09:07");
    expect(time.dynamic_fields).toMatchObject({ font_substitution: "FONT081", hour: "09", minute: "07" });
    expect(time.font_override).toBe("FONT081");
    const identitySticker = createDynamicStickerElement("DYN016", context, identity, 0);
    expect(identitySticker.formatted_value).toBe("@dada");
    expect(normalizeSocialId("@@@dada")).toBe("@dada");
  });

  it("uses the archived template categories and only the fields visibly consumed by each source", () => {
    expect(P0A_DYNAMIC_STICKERS.map(({ category, templateId }) => [templateId, category])).toEqual([
      ["DYN001", "location"], ["DYN002", "location"], ["DYN003", "location"], ["DYN004", "location"],
      ["DYN007", "other"], ["DYN008", "time"], ["DYN011", "time"], ["DYN012", "time"],
      ["DYN015", "identity"], ["DYN016", "identity"],
    ]);
    const other = createDynamicStickerElement("DYN007", context, identity, 0);
    expect(other.dynamic_fields).toEqual({ nickname: "@dada" });
    expect(other.formatted_value).toBe("@dada");
    const monthTime = createDynamicStickerElement("DYN008", context, identity, 0);
    expect(monthTime.dynamic_fields).toEqual({ hour: "09", minute: "07", month: "08" });
  });

  it("maps every enabled dynamic sticker to its archived source candidate and original resource version", () => {
    expect(Object.entries(DYNAMIC_RENDER_MODELS).map(([id, model]) => [id, model.sourceCandidateId])).toEqual([
      ["DYN001", "l_POI01"], ["DYN002", "l_POI02"], ["DYN003", "l_POI03"], ["DYN004", "l_POI04"],
      ["DYN007", "diaoyu"], ["DYN008", "l_shijian2"], ["DYN011", "l_shijian6"], ["DYN012", "l_shijian7"],
      ["DYN015", "0721userna"], ["DYN016", "l_username00"],
    ]);
    const element = createDynamicStickerElement("DYN001", { ...context, location: { formattedValue: "温州" } }, identity, 0);
    expect(element.resource_version).toBe(DYNAMIC_RESOURCE_VERSION);
    expect(DYNAMIC_RENDER_MODELS.DYN001.imageLayers).toEqual([
      { assetId: "DYN001-image28", height: 67, width: 219, x: -17.562, y: 2.203 },
    ]);
  });

  it("keeps the source DYN012 split-clock composition instead of a generic time box", () => {
    expect(DYN012_RENDER_LAYOUT).toEqual({
      background: "transparent",
      divider: { color: "#FFFFFF", height: 42, width: 7, x: 0, y: 0 },
      hour: { color: "#FFFFFF", fontId: "FONT081", fontSize: 100, x: -60, y: 0 },
      meridiem: { color: "#FFFFFF", fontId: "FONT081", fontSize: 20, x: 94, y: -65 },
      minute: { color: "#FFFFFF", fontId: "FONT081", fontSize: 100, x: 60, y: 0 },
    });
  });

  it("keeps a per-instance override separate from the account profile", () => {
    const profile = structuredClone(context.profile);
    const element = createDynamicStickerElement("DYN015", context, identity, 0);
    const changed = overrideDynamicStickerValue(element, "Single Sticker Name");
    expect(changed.formatted_value).toBe("Single Sticker Name");
    expect(element.formatted_value).toBe("Dada Creator");
    expect(context.profile).toEqual(profile);
  });

  it("does not call location or reverse geocoding until Dada consent is confirmed", async () => {
    const geolocate = vi.fn(async () => ({ latitude: 27.9943, longitude: 120.6994 }));
    const reverseGeocode = vi.fn(async () => "浙江省温州市");
    const gate = new LocationConsentGate({ geolocate, reverseGeocode });
    expect(gate.reject()).toBeUndefined();
    expect(geolocate).not.toHaveBeenCalled();
    expect(reverseGeocode).not.toHaveBeenCalled();
    await expect(gate.confirm()).resolves.toEqual({ formattedValue: "浙江省温州市", latitude: 27.9943, longitude: 120.6994 });
    expect(geolocate).toHaveBeenCalledTimes(1);
    expect(reverseGeocode).toHaveBeenCalledTimes(1);
  });
});
