import { describe, expect, it } from "vitest";
import {
  MAX_BULK_DOWNLOAD_PROPERTIES,
  buildBulkDownloadRecord,
  buildSelectionConfig,
  bulkDownloadRefetchInterval,
  parseDelivery,
} from "./model";

describe("buildSelectionConfig — UI selection → stored config", () => {
  const portfolioId = 814;

  it("one project + include all → project_codes", () => {
    const r = buildSelectionConfig({
      scope: { kind: "project", projectCode: "ABRI-2024-01" },
      includeAll: true,
      landlordPropertyIds: [],
      portfolioId,
    });
    expect(r).toEqual({
      ok: true,
      config: { project_codes: ["ABRI-2024-01"], portfolio_id: 814 },
    });
  });

  it("one project + ticked subset → landlord_property_ids (scope ignored)", () => {
    const r = buildSelectionConfig({
      scope: { kind: "project", projectCode: "ABRI-2024-01" },
      includeAll: false,
      landlordPropertyIds: ["LP1023", "LP1044"],
      portfolioId,
    });
    expect(r).toEqual({
      ok: true,
      config: { landlord_property_ids: ["LP1023", "LP1044"], portfolio_id: 814 },
    });
  });

  it("all projects + include all → every project code", () => {
    const r = buildSelectionConfig({
      scope: { kind: "all-projects", projectCodes: ["A-1", "B-2"] },
      includeAll: true,
      landlordPropertyIds: [],
      portfolioId,
    });
    expect(r).toEqual({
      ok: true,
      config: { project_codes: ["A-1", "B-2"], portfolio_id: 814 },
    });
  });

  it("all projects + ticked subset → landlord_property_ids", () => {
    const r = buildSelectionConfig({
      scope: { kind: "all-projects", projectCodes: ["A-1", "B-2"] },
      includeAll: false,
      landlordPropertyIds: ["LP1"],
      portfolioId,
    });
    expect(r).toEqual({
      ok: true,
      config: { landlord_property_ids: ["LP1"], portfolio_id: 814 },
    });
  });

  it("de-duplicates ids and codes", () => {
    expect(
      buildSelectionConfig({
        scope: { kind: "all-projects", projectCodes: ["A-1", "A-1"] },
        includeAll: true,
        landlordPropertyIds: [],
        portfolioId,
      }),
    ).toEqual({ ok: true, config: { project_codes: ["A-1"], portfolio_id: 814 } });

    expect(
      buildSelectionConfig({
        scope: { kind: "project", projectCode: "A-1" },
        includeAll: false,
        landlordPropertyIds: ["LP1", "LP1", " LP1 "],
        portfolioId,
      }),
    ).toEqual({
      ok: true,
      config: { landlord_property_ids: ["LP1"], portfolio_id: 814 },
    });
  });

  it("rejects an empty subset", () => {
    const r = buildSelectionConfig({
      scope: { kind: "project", projectCode: "A-1" },
      includeAll: false,
      landlordPropertyIds: [],
      portfolioId,
    });
    expect(r.ok).toBe(false);
  });

  it("rejects include-all with no usable codes", () => {
    const r = buildSelectionConfig({
      scope: { kind: "all-projects", projectCodes: ["", "  "] },
      includeAll: true,
      landlordPropertyIds: [],
      portfolioId,
    });
    expect(r.ok).toBe(false);
  });

  it("rejects a subset over the 500 cap with the detail message", () => {
    const ids = Array.from({ length: 501 }, (_, i) => `LP${i}`);
    const r = buildSelectionConfig({
      scope: { kind: "project", projectCode: "A-1" },
      includeAll: false,
      landlordPropertyIds: ids,
      portfolioId,
    });
    expect(r.ok).toBe(false);
    if (!r.ok) {
      expect(r.error).toContain(`over the ${MAX_BULK_DOWNLOAD_PROPERTIES} limit`);
      expect(r.error).toContain("501 properties");
    }
  });
});

describe("buildBulkDownloadRecord", () => {
  it("writes the app-owned task marker and the config as JSON inputs", () => {
    const record = buildBulkDownloadRecord({
      portfolioId: "814",
      config: { project_codes: ["A-1"], portfolio_id: 814 },
    });
    expect(record.taskSource).toBe("app:bulk_document_download");
    expect(record.source).toBe("portfolio_id");
    expect(record.sourceId).toBe("814");
    expect(record.status).toBe("waiting");
    expect(JSON.parse(record.inputs)).toEqual({
      project_codes: ["A-1"],
      portfolio_id: 814,
    });
  });
});


describe("parseDelivery", () => {
  it("reads a finished delivery", () => {
    const d = parseDelivery(
      JSON.stringify({
        presigned_url: "https://x",
        package_s3_key: "k",
        included: 137,
        skipped: [{ landlord_property_id: "LP9", s3_key: "k", reason: "unreadable" }],
      }),
    );
    expect(d).toEqual({
      presignedUrl: "https://x",
      packageS3Key: "k",
      included: 137,
      skipped: [{ landlord_property_id: "LP9", s3_key: "k", reason: "unreadable" }],
    });
  });

  it("tolerates link-key naming variants", () => {
    expect(parseDelivery(JSON.stringify({ presignedUrl: "https://a" }))?.presignedUrl).toBe(
      "https://a",
    );
    expect(parseDelivery(JSON.stringify({ download_url: "https://b" }))?.presignedUrl).toBe(
      "https://b",
    );
    expect(parseDelivery(JSON.stringify({ url: "https://c", s3_key: "k" }))).toMatchObject({
      presignedUrl: "https://c",
      packageS3Key: "k",
    });
  });

  it("returns null for in-flight or malformed outputs", () => {
    expect(parseDelivery(null)).toBeNull();
    expect(parseDelivery("")).toBeNull();
    expect(parseDelivery("{not json")).toBeNull();
    expect(parseDelivery(JSON.stringify({ included: 0 }))).toBeNull();
  });
});

describe("bulkDownloadRefetchInterval", () => {
  it("polls while preparing, stops once resolved", () => {
    expect(bulkDownloadRefetchInterval(undefined)).toBe(8_000);
    expect(bulkDownloadRefetchInterval({ state: "preparing", delivery: null })).toBe(8_000);
    expect(
      bulkDownloadRefetchInterval({
        state: "ready",
        delivery: { presignedUrl: "x", packageS3Key: null, included: 1, skipped: [] },
      }),
    ).toBe(false);
    expect(bulkDownloadRefetchInterval({ state: "failed", delivery: null })).toBe(false);
  });
});
