import { describe, test, afterEach } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, rmSync, existsSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
  getISOWeek,
  getCurrentISOWeek,
  getPostsForWeek,
  generateWeeklyReport,
} from "../src/reports/weekly.js";
import { saveBatch, saveWeeklyReport } from "../src/utils/storage.js";
import type { PostAnalytics, AnalyticsBatch } from "../src/models/types.js";

// Helper function to create test post data
function createTestPost(overrides?: Partial<PostAnalytics>): PostAnalytics {
  return {
    id: overrides?.id || "test-post-1",
    title: overrides?.title || "Test post content",
    publishedDate: overrides?.publishedDate || "2026-01-15",
    metrics: {
      impressions: 1000,
      reactions: 50,
      comments: 10,
      shares: 5,
      clicks: 20,
      engagementRate: 8.5,
      ...(overrides?.metrics || {}),
    },
    importedAt: overrides?.importedAt || "2026-01-20T10:00:00Z",
    exportSource: overrides?.exportSource || "test-export.csv",
  };
}

// Helper function to create test batch data
function createTestBatch(overrides?: Partial<AnalyticsBatch>): AnalyticsBatch {
  const posts = overrides?.posts || [createTestPost()];
  return {
    batchId: overrides?.batchId || "12345678-1234-1234-1234-123456789abc",
    importedAt: overrides?.importedAt || "2026-01-20T10:00:00Z",
    exportFilename: overrides?.exportFilename || "test-export.csv",
    dateRange: overrides?.dateRange || { from: "2026-01-15", to: "2026-01-20" },
    postCount: posts.length,
    posts,
  };
}

describe("weekly", () => {
  let tempDir: string;

  // Create temp directory before each test
  function setupTempDir(): string {
    return mkdtempSync(join(tmpdir(), "analytics-test-"));
  }

  // Clean up temp directory after each test
  afterEach(() => {
    if (tempDir && existsSync(tempDir)) {
      rmSync(tempDir, { recursive: true, force: true });
    }
  });

  describe("getISOWeek", () => {
    test("should return correct ISO week for 2026-01-01", () => {
      // 2026-01-01 is Thursday, should be week 1 of 2026
      const result = getISOWeek(new Date("2026-01-01"));
      assert.equal(result, "2026-W01");
    });

    test("should return correct ISO week for 2025-12-29", () => {
      // 2025-12-29 is Monday, first day of ISO week 1, 2026
      const result = getISOWeek(new Date("2025-12-29"));
      assert.equal(result, "2026-W01");
    });

    test("should return correct ISO week for 2025-12-28", () => {
      // 2025-12-28 is Sunday, last day of 2025 week 52
      const result = getISOWeek(new Date("2025-12-28"));
      assert.equal(result, "2025-W52");
    });

    test("should handle year boundaries - early January", () => {
      // 2025-01-01 is Wednesday, should be in 2025-W01
      const result = getISOWeek(new Date("2025-01-01"));
      assert.equal(result, "2025-W01");
    });

    test("should handle year boundaries - late December", () => {
      // 2024-12-30 is Monday, should be in 2025-W01
      const result = getISOWeek(new Date("2024-12-30"));
      assert.equal(result, "2025-W01");
    });

    test("should handle mid-year dates", () => {
      // 2026-06-15 is Monday
      const result = getISOWeek(new Date("2026-06-15"));
      assert.equal(result, "2026-W25");
    });

    test("should handle leap year", () => {
      // 2024 is a leap year, Feb 29 should be in week 9
      const result = getISOWeek(new Date("2024-02-29"));
      assert.equal(result, "2024-W09");
    });
  });

  describe("getCurrentISOWeek", () => {
    test("should return a valid ISO week format", () => {
      const result = getCurrentISOWeek();

      // Should match YYYY-WXX format
      assert.match(result, /^\d{4}-W\d{2}$/);
    });

    test("should return a week in reasonable range", () => {
      const result = getCurrentISOWeek();
      const year = parseInt(result.split("-")[0]);
      const week = parseInt(result.split("-W")[1]);

      // Year should be current or adjacent
      const currentYear = new Date().getFullYear();
      assert.ok(year >= currentYear - 1 && year <= currentYear + 1);

      // Week should be 1-53
      assert.ok(week >= 1 && week <= 53);
    });
  });

  describe("getPostsForWeek", () => {
    test("should filter posts to correct week", () => {
      const posts: PostAnalytics[] = [
        createTestPost({
          id: "post1",
          publishedDate: "2026-01-05", // 2026-W02
        }),
        createTestPost({
          id: "post2",
          publishedDate: "2026-01-12", // 2026-W03
        }),
        createTestPost({
          id: "post3",
          publishedDate: "2026-01-13", // 2026-W03
        }),
        createTestPost({
          id: "post4",
          publishedDate: "2026-01-19", // 2026-W04
        }),
      ];

      const week3Posts = getPostsForWeek(posts, "2026-W03");

      assert.equal(week3Posts.length, 2);
      assert.ok(week3Posts.some(p => p.id === "post2"));
      assert.ok(week3Posts.some(p => p.id === "post3"));
    });

    test("should return empty for weeks with no posts", () => {
      const posts: PostAnalytics[] = [
        createTestPost({
          id: "post1",
          publishedDate: "2026-01-05", // 2026-W02
        }),
      ];

      const result = getPostsForWeek(posts, "2026-W03");

      assert.deepEqual(result, []);
    });

    test("should handle empty posts array", () => {
      const result = getPostsForWeek([], "2026-W03");

      assert.deepEqual(result, []);
    });

    test("should handle posts across year boundary", () => {
      const posts: PostAnalytics[] = [
        createTestPost({
          id: "post1",
          publishedDate: "2025-12-29", // 2026-W01 (Monday)
        }),
        createTestPost({
          id: "post2",
          publishedDate: "2026-01-01", // 2026-W01 (Thursday)
        }),
        createTestPost({
          id: "post3",
          publishedDate: "2025-12-28", // 2025-W52 (Sunday)
        }),
      ];

      const week1Posts = getPostsForWeek(posts, "2026-W01");

      assert.equal(week1Posts.length, 2);
      assert.ok(week1Posts.some(p => p.id === "post1"));
      assert.ok(week1Posts.some(p => p.id === "post2"));
    });
  });

  describe("generateWeeklyReport", () => {
    test("should handle no posts", () => {
      tempDir = setupTempDir();

      const report = generateWeeklyReport(tempDir, "2026-W03");

      assert.equal(report.week, "2026-W03");
      assert.equal(report.summary.totalPosts, 0);
      assert.equal(report.summary.totalImpressions, 0);
      assert.equal(report.summary.avgEngagementRate, 0);
      assert.deepEqual(report.topPerformers, []);
      assert.deepEqual(report.underperformers, []);
    });

    test("should calculate correct summary metrics", () => {
      tempDir = setupTempDir();

      const posts: PostAnalytics[] = [
        createTestPost({
          id: "post1",
          publishedDate: "2026-01-12", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 8.5,
          },
        }),
        createTestPost({
          id: "post2",
          publishedDate: "2026-01-13", // 2026-W03
          metrics: {
            impressions: 2000,
            reactions: 100,
            comments: 20,
            shares: 10,
            clicks: 40,
            engagementRate: 8.5,
          },
        }),
        createTestPost({
          id: "post3",
          publishedDate: "2026-01-14", // 2026-W03
          metrics: {
            impressions: 1500,
            reactions: 75,
            comments: 15,
            shares: 7,
            clicks: 30,
            engagementRate: 8.47,
          },
        }),
      ];

      const batch = createTestBatch({
        dateRange: { from: "2026-01-12", to: "2026-01-14" },
        posts,
      });

      saveBatch(tempDir, batch);

      const report = generateWeeklyReport(tempDir, "2026-W03");

      assert.equal(report.week, "2026-W03");
      assert.equal(report.summary.totalPosts, 3);
      assert.equal(report.summary.totalImpressions, 4500);
      assert.equal(report.summary.totalReactions, 225);
      assert.equal(report.summary.totalComments, 45);
      assert.equal(report.summary.totalShares, 22);
      assert.equal(report.summary.totalClicks, 90);
      assert.equal(report.summary.avgImpressionsPerPost, 1500);

      // Average engagement rate: (8.5 + 8.5 + 8.47) / 3 ≈ 8.49
      assert.ok(Math.abs(report.summary.avgEngagementRate - 8.49) < 0.01);
    });

    test("should identify top performers and underperformers", () => {
      tempDir = setupTempDir();

      const posts: PostAnalytics[] = [
        createTestPost({
          id: "high1",
          publishedDate: "2026-01-12", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 10.0,
          },
        }),
        createTestPost({
          id: "high2",
          publishedDate: "2026-01-13", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 9.0,
          },
        }),
        createTestPost({
          id: "medium",
          publishedDate: "2026-01-13", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 5.0,
          },
        }),
        createTestPost({
          id: "low1",
          publishedDate: "2026-01-14", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 3.0,
          },
        }),
        createTestPost({
          id: "low2",
          publishedDate: "2026-01-14", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 2.0,
          },
        }),
      ];

      const batch = createTestBatch({
        dateRange: { from: "2026-01-12", to: "2026-01-14" },
        posts,
      });

      saveBatch(tempDir, batch);

      const report = generateWeeklyReport(tempDir, "2026-W03");

      // Top 3 performers (highest engagement)
      assert.equal(report.topPerformers.length, 3);
      assert.equal(report.topPerformers[0].id, "high1"); // 10.0
      assert.equal(report.topPerformers[1].id, "high2"); // 9.0
      assert.equal(report.topPerformers[2].id, "medium"); // 5.0

      // Bottom 3 underperformers (lowest engagement)
      assert.equal(report.underperformers.length, 3);
      assert.equal(report.underperformers[0].id, "low2"); // 2.0
      assert.equal(report.underperformers[1].id, "low1"); // 3.0
      assert.equal(report.underperformers[2].id, "medium"); // 5.0
    });

    test("should handle fewer than 3 posts", () => {
      tempDir = setupTempDir();

      const posts: PostAnalytics[] = [
        createTestPost({
          id: "post1",
          publishedDate: "2026-01-12", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 10.0,
          },
        }),
        createTestPost({
          id: "post2",
          publishedDate: "2026-01-13", // 2026-W03
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 5.0,
          },
        }),
      ];

      const batch = createTestBatch({
        dateRange: { from: "2026-01-12", to: "2026-01-13" },
        posts,
      });

      saveBatch(tempDir, batch);

      const report = generateWeeklyReport(tempDir, "2026-W03");

      // Should have 2 top performers
      assert.equal(report.topPerformers.length, 2);
      // Should have 2 underperformers (same posts, reversed)
      assert.equal(report.underperformers.length, 2);
    });

    test("should calculate trends when previous week data exists", () => {
      tempDir = setupTempDir();

      // Create previous week data
      const prevWeekPosts: PostAnalytics[] = [
        createTestPost({
          id: "prev1",
          publishedDate: "2026-01-05", // 2026-W02
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 8.5,
          },
        }),
        createTestPost({
          id: "prev2",
          publishedDate: "2026-01-06", // 2026-W02
          metrics: {
            impressions: 1000,
            reactions: 50,
            comments: 10,
            shares: 5,
            clicks: 20,
            engagementRate: 8.5,
          },
        }),
      ];

      const prevBatch = createTestBatch({
        dateRange: { from: "2026-01-05", to: "2026-01-06" },
        posts: prevWeekPosts,
      });

      saveBatch(tempDir, prevBatch);

      // Generate previous week report
      generateWeeklyReport(tempDir, "2026-W02");

      // Create current week data with higher metrics
      const currentWeekPosts: PostAnalytics[] = [
        createTestPost({
          id: "curr1",
          publishedDate: "2026-01-12", // 2026-W03
          metrics: {
            impressions: 1500,
            reactions: 75,
            comments: 15,
            shares: 7,
            clicks: 30,
            engagementRate: 8.47,
          },
        }),
        createTestPost({
          id: "curr2",
          publishedDate: "2026-01-13", // 2026-W03
          metrics: {
            impressions: 1500,
            reactions: 75,
            comments: 15,
            shares: 7,
            clicks: 30,
            engagementRate: 8.47,
          },
        }),
      ];

      const currentBatch = createTestBatch({
        dateRange: { from: "2026-01-12", to: "2026-01-13" },
        posts: currentWeekPosts,
      });

      saveBatch(tempDir, currentBatch);

      const report = generateWeeklyReport(tempDir, "2026-W03");

      // Current impressions: 3000, Previous: 2000 → 50% increase
      assert.equal(report.trends.comparedTo, "2026-W02");
      assert.equal(report.trends.impressionsTrend, "up");
      assert.equal(report.trends.percentChange.impressions, 50);

      // Engagement rate essentially the same
      assert.equal(report.trends.engagementTrend, "stable");
    });

    test("should default to stable trends when no previous week data", () => {
      tempDir = setupTempDir();

      const posts: PostAnalytics[] = [
        createTestPost({
          id: "post1",
          publishedDate: "2026-01-12", // 2026-W03
        }),
      ];

      const batch = createTestBatch({
        dateRange: { from: "2026-01-12", to: "2026-01-12" },
        posts,
      });

      saveBatch(tempDir, batch);

      const report = generateWeeklyReport(tempDir, "2026-W03");

      assert.equal(report.trends.impressionsTrend, "stable");
      assert.equal(report.trends.engagementTrend, "stable");
      assert.equal(report.trends.percentChange.impressions, 0);
      assert.equal(report.trends.percentChange.engagement, 0);
    });

    test("should filter posts correctly for target week", () => {
      tempDir = setupTempDir();

      const posts: PostAnalytics[] = [
        createTestPost({
          id: "w02-post",
          publishedDate: "2026-01-05", // 2026-W02
        }),
        createTestPost({
          id: "w03-post1",
          publishedDate: "2026-01-12", // 2026-W03
        }),
        createTestPost({
          id: "w03-post2",
          publishedDate: "2026-01-13", // 2026-W03
        }),
        createTestPost({
          id: "w04-post",
          publishedDate: "2026-01-19", // 2026-W04
        }),
      ];

      const batch = createTestBatch({
        dateRange: { from: "2026-01-05", to: "2026-01-19" },
        posts,
      });

      saveBatch(tempDir, batch);

      const report = generateWeeklyReport(tempDir, "2026-W03");

      // Should only include W03 posts
      assert.equal(report.summary.totalPosts, 2);
      assert.equal(report.topPerformers.length, 2);
      assert.ok(report.topPerformers.some(p => p.id === "w03-post1"));
      assert.ok(report.topPerformers.some(p => p.id === "w03-post2"));
    });

    test("should use current week if week parameter not provided", () => {
      tempDir = setupTempDir();

      const report = generateWeeklyReport(tempDir);

      // Should match current ISO week format
      assert.match(report.week, /^\d{4}-W\d{2}$/);
    });
  });
});
