import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { BrandKitBundle } from "../services/renderers/types.js";

// ─── Mock playwright-core chromium ─────────────────────────────────────────────

const mockPdf = vi.fn().mockResolvedValue(Buffer.from("%PDF-1.4 fake-pdf"));
const mockSetContent = vi.fn().mockResolvedValue(undefined);
const mockPage = {
  setContent: mockSetContent,
  pdf: mockPdf,
};
const mockBrowserClose = vi.fn().mockResolvedValue(undefined);
const mockBrowser = {
  newPage: vi.fn().mockResolvedValue(mockPage),
  close: mockBrowserClose,
};
const mockChromiumLaunch = vi.fn().mockResolvedValue(mockBrowser);

vi.mock("playwright-core", () => ({
  chromium: {
    launch: mockChromiumLaunch,
  },
}));

// ─── Mock puter-inference ──────────────────────────────────────────────────────

const MOCK_SPEC_JSON = JSON.stringify({
  name: "Acme Corp",
  tagline: "Building the future",
  primaryColor: "#FF5733",
  secondaryColor: "#33C1FF",
  fontStyle: "sans",
  industry: "technology",
  logoDescription: "A stylized rocket silhouette",
});

const MOCK_LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect x="100" y="100" width="312" height="312" fill="#FF5733"/></svg>`;

const MOCK_SIGNATURE_HTML = `<!DOCTYPE html><html><body><p>Email Signature for Acme Corp</p></body></html>`;
const MOCK_LETTERHEAD_HTML = `<!DOCTYPE html><html><body><header>Acme Corp Letterhead</header></body></html>`;
const MOCK_GUIDELINES_HTML = `<!DOCTYPE html><html><body><h1>Acme Corp Brand Guidelines</h1></body></html>`;

const mockPuterChatComplete = vi.fn().mockImplementation(
  (messages: Array<{ role: string; content: string }>) => {
    const systemPrompt = messages.find((m) => m.role === "system")?.content ?? "";
    if (systemPrompt.includes("JSON") || systemPrompt.includes("brand specification")) {
      return Promise.resolve(MOCK_SPEC_JSON);
    }
    if (systemPrompt.includes("SVG") || systemPrompt.includes("logo")) {
      return Promise.resolve(MOCK_LOGO_SVG);
    }
    if (systemPrompt.includes("email signature")) {
      return Promise.resolve(MOCK_SIGNATURE_HTML);
    }
    if (systemPrompt.includes("letterhead")) {
      return Promise.resolve(MOCK_LETTERHEAD_HTML);
    }
    if (systemPrompt.includes("brand guidelines")) {
      return Promise.resolve(MOCK_GUIDELINES_HTML);
    }
    return Promise.resolve(MOCK_SPEC_JSON);
  },
);

vi.mock("../services/puter-inference.js", () => ({
  puterChatComplete: (...args: unknown[]) => mockPuterChatComplete(...args),
}));

// ─── Mock diagram-renderer (for resolveBrowserPath) ───────────────────────────

vi.mock("../services/renderers/diagram-renderer.js", () => ({
  resolveBrowserPath: () => "/fake/chromium",
}));

// ─── Mock icon-renderer (for validateAndCleanSvg) ─────────────────────────────

vi.mock("../services/renderers/icon-renderer.js", () => ({
  validateAndCleanSvg: (raw: string) => ({ svg: raw, valid: true, warnings: [] }),
}));

// ─── Mock sharp ───────────────────────────────────────────────────────────────

const mockToBuffer = vi.fn().mockResolvedValue(Buffer.from("fake-png-data"));
const mockPng = vi.fn().mockReturnValue({ toBuffer: mockToBuffer });
const mockResize = vi.fn().mockReturnValue({ png: mockPng });
const mockSharpInstance = { resize: mockResize };
const mockSharp = vi.fn().mockReturnValue(mockSharpInstance);

vi.mock("sharp", () => ({
  default: (...args: unknown[]) => mockSharp(...args),
}));

// ─── Mock archiver ────────────────────────────────────────────────────────────

const mockArchiverAppend = vi.fn();
const mockArchiverPipe = vi.fn();
const mockArchiverFinalize = vi.fn();
const mockArchiverOn = vi.fn();

vi.mock("archiver", () => ({
  default: (_format: string, _opts: unknown) => {
    const archive = {
      append: mockArchiverAppend,
      pipe: mockArchiverPipe,
      finalize: mockArchiverFinalize,
      on: mockArchiverOn,
    };

    // When pipe is called, simulate the sink getting data and finishing
    mockArchiverPipe.mockImplementation((sink: { write: (chunk: Buffer, enc: string, cb: () => void) => void; emit: (event: string) => void; on: (event: string, cb: () => void) => void }) => {
      // Provide data to sink immediately after finalize is called
      mockArchiverFinalize.mockImplementation(() => {
        // Write PK magic bytes (ZIP signature) to the sink
        const pkBytes = Buffer.from([0x50, 0x4b, 0x03, 0x04, 0x00, 0x00]);
        sink.write(pkBytes, "binary", () => {});
        // Trigger sink finish
        setTimeout(() => sink.emit("finish"), 0);
      });
    });

    return archive;
  },
}));

// ─── Tests ──────────────────────────────────────────────────────────────────────

describe("renderBrandKit", () => {
  let renderBrandKit: (
    input: Record<string, unknown>,
  ) => Promise<import("../services/renderers/types.js").RenderResult>;

  beforeEach(async () => {
    vi.clearAllMocks();

    // Reset mock implementations
    mockPuterChatComplete.mockImplementation(
      (messages: Array<{ role: string; content: string }>) => {
        const systemPrompt = messages.find((m) => m.role === "system")?.content ?? "";
        if (systemPrompt.includes("JSON") || systemPrompt.includes("brand specification")) {
          return Promise.resolve(MOCK_SPEC_JSON);
        }
        if (systemPrompt.includes("SVG") || systemPrompt.includes("logo")) {
          return Promise.resolve(MOCK_LOGO_SVG);
        }
        if (systemPrompt.includes("email signature")) {
          return Promise.resolve(MOCK_SIGNATURE_HTML);
        }
        if (systemPrompt.includes("letterhead")) {
          return Promise.resolve(MOCK_LETTERHEAD_HTML);
        }
        if (systemPrompt.includes("brand guidelines")) {
          return Promise.resolve(MOCK_GUIDELINES_HTML);
        }
        return Promise.resolve(MOCK_SPEC_JSON);
      },
    );

    mockChromiumLaunch.mockResolvedValue(mockBrowser);
    mockBrowser.newPage.mockResolvedValue(mockPage);
    mockPdf.mockResolvedValue(Buffer.from("%PDF-1.4 fake-pdf"));
    mockBrowserClose.mockResolvedValue(undefined);
    mockToBuffer.mockResolvedValue(Buffer.from("fake-png-data"));
    mockPng.mockReturnValue({ toBuffer: mockToBuffer });
    mockResize.mockReturnValue({ png: mockPng });
    mockSharp.mockReturnValue(mockSharpInstance);

    // Reset archiver mocks and re-setup pipe behavior
    mockArchiverPipe.mockImplementation((sink: NodeJS.WritableStream) => {
      mockArchiverFinalize.mockImplementation(() => {
        const ws = sink as unknown as {
          write: (chunk: Buffer, enc: string, cb: () => void) => void;
          emit: (event: string) => void;
        };
        const pkBytes = Buffer.from([0x50, 0x4b, 0x03, 0x04, 0x00, 0x00]);
        ws.write(pkBytes, "binary", () => {});
        setTimeout(() => ws.emit("finish"), 0);
      });
    });

    const mod = await import("../services/renderers/brand-renderer.js");
    renderBrandKit = mod.renderBrandKit;
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it("Test 1: returns brand-kit-bundle with all required top-level fields present", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp, a technology company",
    });

    expect(result.filename).toBe("brand-kit-bundle.json");
    expect(result.contentType).toBe("application/json");

    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;
    expect(bundle.type).toBe("brand-kit-bundle");
    expect(bundle.spec).toBeDefined();
    expect(bundle.logoSvgBase64).toBeDefined();
    expect(bundle.avatarPngs).toBeDefined();
    expect(bundle.socialImages).toBeDefined();
    expect(bundle.signatureHtml).toBeDefined();
    expect(bundle.letterheadHtml).toBeDefined();
    expect(bundle.guidelinesPdfBase64).toBeDefined();
    expect(bundle.zipBase64).toBeDefined();
  });

  it("Test 2: spec contains name, tagline, primaryColor, secondaryColor, fontStyle, industry", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp",
    });
    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;

    expect(typeof bundle.spec.name).toBe("string");
    expect(typeof bundle.spec.tagline).toBe("string");
    expect(typeof bundle.spec.primaryColor).toBe("string");
    expect(typeof bundle.spec.secondaryColor).toBe("string");
    expect(typeof bundle.spec.fontStyle).toBe("string");
    expect(typeof bundle.spec.industry).toBe("string");
    expect(bundle.spec.name.length).toBeGreaterThan(0);
    expect(bundle.spec.primaryColor.length).toBeGreaterThan(0);
  });

  it("Test 3: avatarPngs has keys 512, 256, 128, 64, 32 — all non-empty base64 strings", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp",
    });
    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;

    expect(Object.keys(bundle.avatarPngs)).toEqual(
      expect.arrayContaining(["512", "256", "128", "64", "32"]),
    );
    for (const size of ["512", "256", "128", "64", "32"]) {
      expect(typeof bundle.avatarPngs[size]).toBe("string");
      expect(bundle.avatarPngs[size]!.length).toBeGreaterThan(0);
    }
  });

  it("Test 4: socialImages has keys twitter-profile, twitter-banner, linkedin-profile, linkedin-banner, instagram-profile — all non-empty", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp",
    });
    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;

    const expectedKeys = [
      "twitter-profile",
      "twitter-banner",
      "linkedin-profile",
      "linkedin-banner",
      "instagram-profile",
    ];
    expect(Object.keys(bundle.socialImages)).toEqual(
      expect.arrayContaining(expectedKeys),
    );
    for (const key of expectedKeys) {
      expect(typeof bundle.socialImages[key]).toBe("string");
      expect(bundle.socialImages[key]!.length).toBeGreaterThan(0);
    }
  });

  it("Test 5: signatureHtml and letterheadHtml are non-empty strings", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp",
    });
    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;

    expect(typeof bundle.signatureHtml).toBe("string");
    expect(bundle.signatureHtml.length).toBeGreaterThan(0);
    expect(typeof bundle.letterheadHtml).toBe("string");
    expect(bundle.letterheadHtml.length).toBeGreaterThan(0);
  });

  it("Test 6: guidelinesPdfBase64 is a non-empty string", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp",
    });
    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;

    expect(typeof bundle.guidelinesPdfBase64).toBe("string");
    expect(bundle.guidelinesPdfBase64.length).toBeGreaterThan(0);
  });

  it("Test 7: zipBase64 decodes to a buffer starting with PK (0x50, 0x4B — ZIP magic bytes)", async () => {
    const result = await renderBrandKit({
      prompt: "Create a brand for Acme Corp",
    });
    const bundle = JSON.parse(result.buffer.toString()) as BrandKitBundle;

    expect(typeof bundle.zipBase64).toBe("string");
    expect(bundle.zipBase64.length).toBeGreaterThan(0);

    const decoded = Buffer.from(bundle.zipBase64, "base64");
    expect(decoded[0]).toBe(0x50); // 'P'
    expect(decoded[1]).toBe(0x4b); // 'K'
  });
});
