import type { PostAnalytics, WeeklyReport } from "../models/types.js";
import { mean, trendDirection, percentChange } from "../utils/stats.js";
import { detectAlerts, detectWeeklyAlerts } from "../utils/alerts.js";
import { loadAllPosts, loadWeeklyReport, saveWeeklyReport } from "../utils/storage.js";

/**
 * Get current ISO week string (e.g., "2026-W05").
 * Uses ISO 8601 week date system where Monday is first day of week.
 */
export function getCurrentISOWeek(): string {
  return getISOWeek(new Date());
}

/**
 * Get ISO week string for a specific date.
 * Format: "YYYY-WXX" where XX is zero-padded week number.
 *
 * ISO 8601 week date rules:
 * - Week starts on Monday
 * - Week 1 is the week with the first Thursday of the year
 * - Last week of year might extend into next year
 */
export function getISOWeek(date: Date): string {
  // Copy date to avoid mutating original
  const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));

  // Set to nearest Thursday: current date + 4 - current day number
  // Make Sunday's day number 7
  const dayNum = d.getUTCDay() || 7;
  d.setUTCDate(d.getUTCDate() + 4 - dayNum);

  // Get first day of year
  const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));

  // Calculate full weeks to nearest Thursday
  const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);

  // Return ISO week format
  const year = d.getUTCFullYear();
  const weekStr = weekNo.toString().padStart(2, '0');

  return `${year}-W${weekStr}`;
}

/**
 * Filter posts to a specific ISO week.
 * Posts are matched by converting their publishedDate to ISO week format.
 */
export function getPostsForWeek(posts: PostAnalytics[], week: string): PostAnalytics[] {
  return posts.filter(post => {
    const postDate = new Date(post.publishedDate);
    const postWeek = getISOWeek(postDate);
    return postWeek === week;
  });
}

/**
 * Get the ISO week string for the previous week.
 * Uses proper ISO week calculation to handle year boundaries correctly.
 */
function getPreviousWeek(week: string): string {
  // Parse week string (e.g., "2026-W05")
  const match = week.match(/^(\d{4})-W(\d{2})$/);
  if (!match) {
    throw new Error(`Invalid week format: ${week}`);
  }

  const year = parseInt(match[1]);
  const weekNum = parseInt(match[2]);

  // ISO week 1 is the week containing January 4th
  // Find Thursday of the target ISO week
  const jan4 = new Date(Date.UTC(year, 0, 4));

  // Find Monday of week 1 by going back from Jan 4 to Monday
  const jan4Day = jan4.getUTCDay() || 7; // Sunday = 7 in ISO
  const week1Monday = new Date(jan4.getTime() - (jan4Day - 1) * 24 * 60 * 60 * 1000);

  // Add (weekNum - 1) * 7 days to get Monday of target week
  const targetMonday = new Date(week1Monday.getTime() + (weekNum - 1) * 7 * 24 * 60 * 60 * 1000);

  // Add 3 days to get Thursday of target week
  const targetThursday = new Date(targetMonday.getTime() + 3 * 24 * 60 * 60 * 1000);

  // Subtract 7 days to get previous week's Thursday
  const previousThursday = new Date(targetThursday.getTime() - 7 * 24 * 60 * 60 * 1000);

  // Use getISOWeek to get the correct ISO week string
  return getISOWeek(previousThursday);
}

/**
 * Generate a weekly report from imported analytics data.
 *
 * @param analyticsRoot - Root directory containing analytics data
 * @param week - ISO week string (e.g., "2026-W05"). If not provided, uses current week.
 * @returns WeeklyReport object
 *
 * Process:
 * 1. Load all posts from storage
 * 2. Filter posts for target week
 * 3. Calculate summary metrics
 * 4. Find top 3 performers and bottom 3 underperformers
 * 5. Calculate trends vs previous week
 * 6. Generate alerts
 * 7. Save and return report
 *
 * Edge cases:
 * - No posts for week → zeroed summary
 * - No previous week data → stable trends with 0% change
 * - Fewer than 3 posts → shorter top/bottom lists
 */
export function generateWeeklyReport(analyticsRoot: string, week?: string): WeeklyReport {
  // Determine target week
  const targetWeek = week || getCurrentISOWeek();

  // Load all posts
  const allPosts = loadAllPosts(analyticsRoot);

  // Filter posts for target week
  const weekPosts = getPostsForWeek(allPosts, targetWeek);

  // Initialize report structure
  const report: WeeklyReport = {
    week: targetWeek,
    generatedAt: new Date().toISOString(),
    summary: {
      totalPosts: weekPosts.length,
      totalImpressions: 0,
      totalReactions: 0,
      totalComments: 0,
      totalShares: 0,
      totalClicks: 0,
      avgEngagementRate: 0,
      avgImpressionsPerPost: 0,
    },
    topPerformers: [],
    underperformers: [],
    trends: {
      impressionsTrend: "stable",
      engagementTrend: "stable",
      comparedTo: getPreviousWeek(targetWeek),
      percentChange: {
        impressions: 0,
        engagement: 0,
      },
    },
    alerts: [],
  };

  // If no posts, return early with zeroed report
  if (weekPosts.length === 0) {
    return report;
  }

  // Calculate summary metrics
  let totalSaves = 0;
  let sawSaves = false;
  for (const post of weekPosts) {
    report.summary.totalImpressions += post.metrics.impressions;
    report.summary.totalReactions += post.metrics.reactions;
    report.summary.totalComments += post.metrics.comments;
    report.summary.totalShares += post.metrics.shares;
    report.summary.totalClicks += post.metrics.clicks;
    if (post.metrics.saves !== undefined) {
      totalSaves += post.metrics.saves;
      sawSaves = true;
    }
  }
  // Only surface saves when at least one post carried it — keeps saves-free
  // reports byte-identical to pre-saves output (backward-compat).
  if (sawSaves) {
    report.summary.totalSaves = totalSaves;
  }

  // Calculate averages
  const engagementRates = weekPosts.map(post => post.metrics.engagementRate);
  report.summary.avgEngagementRate = mean(engagementRates);
  report.summary.avgImpressionsPerPost = report.summary.totalImpressions / weekPosts.length;

  // Find top 3 performers (highest engagement rate)
  const sortedByEngagement = [...weekPosts].sort(
    (a, b) => b.metrics.engagementRate - a.metrics.engagementRate
  );
  report.topPerformers = sortedByEngagement.slice(0, 3);

  // Find bottom 3 underperformers (lowest engagement rate)
  report.underperformers = sortedByEngagement
    .slice()
    .reverse()
    .slice(0, 3);

  // Calculate trends vs previous week
  const previousWeek = getPreviousWeek(targetWeek);
  const previousReport = loadWeeklyReport(analyticsRoot, previousWeek);

  if (previousReport && previousReport.summary.totalPosts > 0) {
    // Calculate percent changes
    report.trends.percentChange.impressions = percentChange(
      report.summary.totalImpressions,
      previousReport.summary.totalImpressions
    );

    report.trends.percentChange.engagement = percentChange(
      report.summary.avgEngagementRate,
      previousReport.summary.avgEngagementRate
    );

    // Determine trend directions
    report.trends.impressionsTrend = trendDirection(
      report.summary.totalImpressions,
      previousReport.summary.totalImpressions
    );

    report.trends.engagementTrend = trendDirection(
      report.summary.avgEngagementRate,
      previousReport.summary.avgEngagementRate
    );
  }

  // Generate alerts
  const postAlerts = detectAlerts(weekPosts, "impressions");

  let weeklyAlerts: typeof report.alerts = [];
  if (previousReport && previousReport.summary.totalPosts > 0) {
    weeklyAlerts = detectWeeklyAlerts(
      {
        impressions: report.summary.totalImpressions,
        engagementRate: report.summary.avgEngagementRate,
      },
      {
        impressions: previousReport.summary.totalImpressions,
        engagementRate: previousReport.summary.avgEngagementRate,
      }
    );
  }

  report.alerts = [...weeklyAlerts, ...postAlerts];

  // Save report
  saveWeeklyReport(analyticsRoot, report);

  return report;
}
