import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
import {
    activityDailyPeriodOptions,
    activityTemplateForRuntime,
    canOpenActivityTemplateData,
    createActivityDataInitialRange,
    createActivityTemplateDataCsv,
    defaultActivityDailyPeriod,
    downloadActivityTemplateDataCsv,
    normalizeActivityDataUtcRange,
} from "@/features/activity-templates/dataModel.js";

describe("activity template data model", () => {
    const originalTimeZone = globalThis.process.env.TZ;

    beforeAll(() => {
        globalThis.process.env.TZ = "Asia/Shanghai";
    });

    afterAll(() => {
        if (originalTimeZone) globalThis.process.env.TZ = originalTimeZone;
        else delete globalThis.process.env.TZ;
    });

    test("only exposes data center for a published version with data permission", () => {
        expect(canOpenActivityTemplateData({ publishedVersion: 1 }, { canData: true })).toBe(true);
        expect(canOpenActivityTemplateData({ publishedVersion: 0 }, { canData: true })).toBe(false);
        expect(canOpenActivityTemplateData({ publishedVersion: 2 }, { canData: false })).toBe(false);
    });

    test("maps an Asia/Shanghai three-day calendar selection to the same three UTC day keys", () => {
        const range = normalizeActivityDataUtcRange({
            startMs: Date.parse("2026-07-02T00:00:00+08:00"),
            endMs: Date.parse("2026-07-04T23:59:59.999+08:00"),
        });

        expect(range).toEqual({
            start_ms: Date.parse("2026-07-02T00:00:00Z"),
            end_ms: Date.parse("2026-07-05T00:00:00Z"),
        });
    });

    test("defaults to the latest thirty-day activity window and builds UTC periods", () => {
        const template = {
            startMs: Date.parse("2026-01-01T00:00:00Z"),
            endMs: Date.parse("2026-03-01T00:00:00Z"),
        };
        const now = Date.parse("2026-02-20T12:00:00Z");
        const range = createActivityDataInitialRange(template, now);
        const normalizedRange = normalizeActivityDataUtcRange(range);

        expect(normalizedRange.end_ms - normalizedRange.start_ms).toBe(30 * 24 * 60 * 60 * 1000);
        expect(new Date(normalizedRange.end_ms).toISOString()).toBe("2026-02-21T00:00:00.000Z");
        expect(defaultActivityDailyPeriod(template, now)).toBe("2026-02-20");
        expect(activityDailyPeriodOptions(template, now)[0]).toBe("2026-02-28");
    });

    test("initializes an exact UTC end boundary with the preceding calendar day", () => {
        const template = {
            startMs: Date.parse("2026-07-02T12:00:00Z"),
            endMs: Date.parse("2026-07-05T00:00:00Z"),
        };
        const range = createActivityDataInitialRange(template, Date.parse("2026-07-10T00:00:00Z"));

        expect(normalizeActivityDataUtcRange(range)).toEqual({
            start_ms: Date.parse("2026-07-02T00:00:00Z"),
            end_ms: Date.parse("2026-07-05T00:00:00Z"),
        });
    });

    test("uses the immutable version runtime window for historical data and daily periods", () => {
        const template = activityTemplateForRuntime({
            runtimeFromMs: Date.parse("2026-01-15T12:00:00Z"),
            runtimeToMs: Date.parse("2026-02-01T00:00:00Z"),
            snapshot: {
                startMs: Date.parse("2026-01-01T00:00:00Z"),
                endMs: Date.parse("2026-03-01T00:00:00Z"),
            },
        });

        expect(normalizeActivityDataUtcRange(createActivityDataInitialRange(template, Date.parse("2026-04-01T00:00:00Z")))).toEqual({
            start_ms: Date.parse("2026-01-15T00:00:00Z"),
            end_ms: Date.parse("2026-02-01T00:00:00Z"),
        });
        expect(activityDailyPeriodOptions(template)[0]).toBe("2026-01-31");
    });

    test("keeps the planned end while the selected version is still running", () => {
        const template = activityTemplateForRuntime({
            runtimeFromMs: Date.parse("2026-07-10T08:00:00Z"),
            runtimeToMs: 0,
            snapshot: {
                startMs: Date.parse("2026-07-01T00:00:00Z"),
                endMs: Date.parse("2026-07-31T00:00:00Z"),
            },
        });

        expect(template.startMs).toBe(Date.parse("2026-07-10T08:00:00Z"));
        expect(template.endMs).toBe(Date.parse("2026-07-31T00:00:00Z"));
    });

    test("does not extend statistics past the planned end when a naturally ended version is disabled later", () => {
        const template = activityTemplateForRuntime({
            runtimeFromMs: Date.parse("2026-07-01T00:00:00Z"),
            runtimeToMs: Date.parse("2026-08-03T09:00:00Z"),
            snapshot: {
                startMs: Date.parse("2026-07-01T00:00:00Z"),
                endMs: Date.parse("2026-08-01T00:00:00Z"),
            },
        });

        expect(template.endMs).toBe(Date.parse("2026-08-01T00:00:00Z"));
    });

    test("creates a sectioned CSV from the actual aggregate response", () => {
        const csv = createActivityTemplateDataCsv({
            overview: { participants: 2, visitCount: 3, giftUsers: 1 },
            trend: [{ statDay: "2026-07-01", participants: 2, visitCount: 3 }],
            gifts: [{ giftId: 'gift,"1"', giftUsers: 1 }],
            tasks: [{ taskKey: "visit", taskType: "daily_visit", taskUsers: 2 }],
        });

        expect(csv).toContain("活动概览");
        expect(csv).toContain("每日趋势");
        expect(csv).toContain("访问人次,3");
        expect(csv).toContain('"gift,""1"""');
        expect(csv).toContain("visit,daily_visit,2");
    });

    test("downloads CSV with a BOM for spreadsheet compatibility", () => {
        const click = vi.fn();
        const originalCreateElement = document.createElement.bind(document);
        vi.spyOn(document, "createElement").mockImplementation((tagName, options) => {
            if (tagName === "a") return { click };
            return originalCreateElement(tagName, options);
        });
        vi.spyOn(window.URL, "createObjectURL").mockReturnValue("blob:data");
        vi.spyOn(window.URL, "revokeObjectURL").mockImplementation(() => {});

        downloadActivityTemplateDataCsv({ overview: {}, trend: [], gifts: [], tasks: [] }, "activity.csv");

        expect(click).toHaveBeenCalledOnce();
        expect(window.URL.revokeObjectURL).toHaveBeenCalledWith("blob:data");
    });
});
