import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
import log from "@/lib/logger";
import type {
  UserReport,
  WeeklyCheckInStats,
  NutritionSummary,
  HydrationSummary,
  GoalSummary,
  ProfileChangeSummary,
  RecommendationSummary,
  AttendanceType,
} from "@fitai/shared";
import { ATTENDANCE_TYPES } from "@fitai/shared";
import { generateReportPDFBase64 } from "@/lib/pdf/report-helpers";

/**
 * GET /api/reports/user/[userId]
 * Generate a comprehensive user report
 *
 * Query params:
 * - userId: string (required) - Target user ID
 * - startDate: YYYY-MM-DD (optional, default: 30 days ago)
 * - endDate: YYYY-MM-DD (optional, default: today)
 *
 * Access Control:
 * - Admins: Can generate reports for any user in their gym
 * - Trainers: Can generate reports for their assigned clients only
 * - Users: Can only generate reports for themselves
 */
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ userId: string }> },
) {
  try {
    const { userId: clerkUserId } = await auth();
    if (!clerkUserId) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const db = await getDatabase();
    const currentUser = await ensureUserSynced(clerkUserId, db);

    if (!currentUser) {
      return NextResponse.json({ error: "Forbidden" }, { status: 403 });
    }

    const { userId: targetUserId } = await params;

    // Access Control Check
    const hasAccess = await checkReportAccess(currentUser, targetUserId, db);
    if (!hasAccess) {
      return NextResponse.json(
        { error: "You don't have permission to view this report" },
        { status: 403 },
      );
    }

    // Parse date range from query params
    const { searchParams } = new URL(request.url);
    const endDate =
      searchParams.get("endDate") || new Date().toISOString().split("T")[0];
    const startDate =
      searchParams.get("startDate") ||
      new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
        .toISOString()
        .split("T")[0];
    const format = searchParams.get("format") || "json";

    // Validate dates
    if (isNaN(Date.parse(startDate)) || isNaN(Date.parse(endDate))) {
      return NextResponse.json(
        { error: "Invalid date format. Use YYYY-MM-DD" },
        { status: 400 },
      );
    }

    // Fetch all user data
    const [user, client, fitnessProfile] = await Promise.all([
      db.getUserById(targetUserId),
      db.getClientByUserId(targetUserId),
      db.getFitnessProfileByUserId(targetUserId),
    ]);

    if (!user) {
      return NextResponse.json({ error: "User not found" }, { status: 404 });
    }

    // Aggregate data in parallel
    const [
      nutritionRecords,
      hydrationRecords,
      fitnessGoals,
      profileHistory,
      recommendations,
    ] = await Promise.all([
      db.getDailyNutritionRange(targetUserId, startDate, endDate),
      db.getDailyHydrationRange(targetUserId, startDate, endDate),
      db.getFitnessGoalsByUserId(targetUserId),
      db.getFitnessProfileHistory(
        targetUserId,
        new Date(startDate),
        new Date(endDate),
      ),
      db.getRecommendationsByUserId(targetUserId),
    ]);

    // Process and aggregate data
    const weeklyCheckIns = await processWeeklyCheckIns(
      db,
      targetUserId,
      startDate,
      endDate,
    );
    const nutrition = processNutrition(nutritionRecords);
    const hydration = processHydration(hydrationRecords);
    const goals = processGoals(fitnessGoals);
    const profileChanges = processProfileHistory(profileHistory);
    const recs = processRecommendations(recommendations);

    const report: UserReport = {
      userId: targetUserId,
      user,
      client,
      fitnessProfile,
      reportPeriod: {
        startDate,
        endDate,
      },
      weeklyCheckIns,
      nutrition,
      hydration,
      goals,
      profileHistory: profileChanges,
      recommendations: recs,
      generatedAt: new Date(),
    };

    // Return PDF if requested
    if (format === "pdf") {
      try {
        const pdfBase64 = generateReportPDFBase64(report);
        const pdfBuffer = Buffer.from(pdfBase64, "base64");

        return new NextResponse(pdfBuffer, {
          status: 200,
          headers: {
            "Content-Type": "application/pdf",
            "Content-Disposition": `attachment; filename="FitAI_Report_${report.user.firstName}_${report.user.lastName}_${startDate}_${endDate}.pdf"`,
            "Content-Length": pdfBuffer.length.toString(),
          },
        });
      } catch (pdfError) {
        log.error("Failed to generate PDF", pdfError);
        return NextResponse.json(
          { error: "Failed to generate PDF" },
          { status: 500 },
        );
      }
    }

    return NextResponse.json(report);
  } catch (error) {
    log.error("Failed to generate user report", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 },
    );
  }
}

/**
 * Check if the current user has access to view the target user's report
 */
async function checkReportAccess(
  currentUser: { id: string; role: string; gymId?: string },
  targetUserId: string,
  db: Awaited<ReturnType<typeof getDatabase>>,
): Promise<boolean> {
  // Users can always view their own reports
  if (currentUser.id === targetUserId) {
    return true;
  }

  // Admins can view any user's report in their gym
  if (currentUser.role === "admin" || currentUser.role === "superAdmin") {
    const targetUser = await db.getUserById(targetUserId);
    if (!targetUser) return false;

    // SuperAdmins can view all users
    if (currentUser.role === "superAdmin") return true;

    // Admins can only view users in their gym
    if (currentUser.gymId && targetUser.gymId === currentUser.gymId) {
      return true;
    }
    return false;
  }

  // Trainers can only view reports for their assigned clients
  if (currentUser.role === "trainer") {
    const assignments = await db.getTrainerClientAssignments(currentUser.id);
    const assignment = assignments.find(
      (a) => a.clientId === targetUserId && a.isActive,
    );
    return !!assignment;
  }

  // Regular users cannot view other users' reports
  return false;
}

/**
 * Process attendance records into weekly check-in stats
 */
async function processWeeklyCheckIns(
  db: Awaited<ReturnType<typeof getDatabase>>,
  userId: string,
  startDate: string,
  endDate: string,
): Promise<WeeklyCheckInStats[]> {
  const weeks: WeeklyCheckInStats[] = [];

  // Generate all weeks in the range
  const current = new Date(startDate);
  const end = new Date(endDate);

  while (current <= end) {
    // Get Monday of current week (ISO week)
    const monday = new Date(current);
    const day = monday.getDay();
    const diff = monday.getDate() - day + (day === 0 ? -6 : 1);
    monday.setDate(diff);

    const weekStart = monday.toISOString().split("T")[0];
    const sunday = new Date(monday);
    sunday.setDate(sunday.getDate() + 6);
    const weekEnd = sunday.toISOString().split("T")[0];

    weeks.push({
      weekStart,
      weekEnd,
      totalCheckIns: 0,
      totalTimeMinutes: 0,
      averageDurationMinutes: 0,
      checkInsByType: ATTENDANCE_TYPES.map((type) => ({ type, count: 0 })),
    });

    current.setDate(current.getDate() + 7);
  }

  // Fetch attendance for each week and aggregate
  for (let i = 0; i < weeks.length; i++) {
    const week = weeks[i];
    const attendanceRecords = await db.getAttendanceByWeek(
      userId,
      new Date(week.weekStart),
    );

    // Filter records to only include those within the week
    const weekStartDate = new Date(week.weekStart);
    const weekEndDate = new Date(week.weekEnd);
    weekEndDate.setHours(23, 59, 59, 999);

    const filteredRecords = attendanceRecords.filter((record) => {
      const checkInTime = new Date(record.checkInTime);
      return checkInTime >= weekStartDate && checkInTime <= weekEndDate;
    });

    week.totalCheckIns = filteredRecords.length;

    // Calculate time spent
    for (const record of filteredRecords) {
      if (record.checkOutTime) {
        const checkIn = new Date(record.checkInTime);
        const checkOut = new Date(record.checkOutTime);
        const durationMs = checkOut.getTime() - checkIn.getTime();
        const durationMinutes = Math.floor(durationMs / 60000);
        week.totalTimeMinutes += durationMinutes;
      }

      // Count by type
      const typeIndex = week.checkInsByType.findIndex(
        (t) => t.type === record.type,
      );
      if (typeIndex !== -1) {
        week.checkInsByType[typeIndex].count++;
      }
    }

    // Calculate average duration
    week.averageDurationMinutes =
      week.totalCheckIns > 0
        ? Math.round(week.totalTimeMinutes / week.totalCheckIns)
        : 0;
  }

  return weeks.sort((a, b) => a.weekStart.localeCompare(b.weekStart));
}

/**
 * Process nutrition records into summaries
 */
function processNutrition(records: any[]): UserReport["nutrition"] {
  const dailySummaries: NutritionSummary[] = records.map((record) => ({
    date: record.date,
    totalCalories: record.totalCalories,
    calorieGoal: record.calorieGoal,
    caloriesDelta: record.totalCalories - record.calorieGoal,
    mealsCount: record.meals?.length || 0,
  }));

  const totalDays = dailySummaries.length;
  const totalCalories = dailySummaries.reduce(
    (sum, day) => sum + day.totalCalories,
    0,
  );
  const averageDailyCalories =
    totalDays > 0 ? Math.round(totalCalories / totalDays) : 0;

  // Count days where calories were within ±10% of goal
  const daysMetGoal = dailySummaries.filter((day) => {
    const lowerBound = day.calorieGoal * 0.9;
    const upperBound = day.calorieGoal * 1.1;
    return day.totalCalories >= lowerBound && day.totalCalories <= upperBound;
  }).length;

  return {
    dailySummaries,
    averageDailyCalories,
    totalDays,
    daysMetGoal,
  };
}

/**
 * Process hydration records into summaries
 */
function processHydration(records: any[]): UserReport["hydration"] {
  const dailySummaries: HydrationSummary[] = records.map((record) => ({
    date: record.date,
    totalWater: record.totalWater,
    waterGoal: record.waterGoal,
    hydrationPercentage:
      record.waterGoal > 0
        ? Math.round((record.totalWater / record.waterGoal) * 100)
        : 0,
  }));

  const totalDays = dailySummaries.length;
  const totalWater = dailySummaries.reduce(
    (sum, day) => sum + day.totalWater,
    0,
  );
  const averageDailyWater =
    totalDays > 0 ? Math.round(totalWater / totalDays) : 0;

  // Count days where water goal was met (>= 100%)
  const daysMetGoal = dailySummaries.filter(
    (day) => day.hydrationPercentage >= 100,
  ).length;

  return {
    dailySummaries,
    averageDailyWater,
    totalDays,
    daysMetGoal,
  };
}

/**
 * Process fitness goals into summaries
 */
function processGoals(goals: any[]): GoalSummary {
  const active = goals.filter((g) => g.status === "active");
  const completed = goals.filter((g) => g.status === "completed");

  const averageProgress =
    active.length > 0
      ? Math.round(
          active.reduce((sum, g) => sum + g.progress, 0) / active.length,
        )
      : 0;

  return {
    active,
    completed,
    totalActive: active.length,
    totalCompleted: completed.length,
    averageProgress,
  };
}

/**
 * Process fitness profile history into summaries
 */
function processProfileHistory(history: any[]): ProfileChangeSummary[] {
  return history.map((record) => ({
    changeType: record.changeType,
    fieldName: record.fieldName,
    previousValue: record.previousValue,
    newValue: record.newValue,
    changedAt: record.changedAt,
  }));
}

/**
 * Process recommendations into summaries
 */
function processRecommendations(recommendations: any[]): RecommendationSummary {
  const accepted = recommendations.filter((r) => r.status === "accepted");
  const rejected = recommendations.filter((r) => r.status === "rejected");
  const pending = recommendations.filter((r) => r.status === "pending");

  return {
    accepted,
    rejected,
    pending,
    totalAccepted: accepted.length,
    totalRejected: rejected.length,
    totalPending: pending.length,
  };
}
