/**
 * Report Generation API Tests
 *
 * Tests for /api/reports/user/[userId] endpoint
 *
 * Test Coverage:
 * 1. Access Control (client, trainer, admin, superAdmin)
 * 2. Data Aggregation (attendance, nutrition, hydration, goals)
 * 3. PDF Generation
 * 4. Date Range Validation
 */

import { describe, it, expect, beforeAll, afterAll } from "@jest/globals";

// Mock database
const mockDatabase = {
  getUserById: jest.fn(),
  getClientByUserId: jest.fn(),
  getFitnessProfileByUserId: jest.fn(),
  getAttendanceByWeek: jest.fn(),
  getDailyNutritionRange: jest.fn(),
  getDailyHydrationRange: jest.fn(),
  getFitnessGoalsByUserId: jest.fn(),
  getFitnessProfileHistory: jest.fn(),
  getRecommendationsByUserId: jest.fn(),
  getTrainerClientAssignments: jest.fn(),
};

// Mock user data
const mockUsers = {
  client: {
    id: "user_client_123",
    email: "client@example.com",
    firstName: "John",
    lastName: "Client",
    role: "client",
    gymId: "gym_123",
  },
  trainer: {
    id: "user_trainer_456",
    email: "trainer@example.com",
    firstName: "Jane",
    lastName: "Trainer",
    role: "trainer",
    gymId: "gym_123",
  },
  admin: {
    id: "user_admin_789",
    email: "admin@example.com",
    firstName: "Admin",
    lastName: "User",
    role: "admin",
    gymId: "gym_123",
  },
  superAdmin: {
    id: "user_superadmin_000",
    email: "superadmin@example.com",
    firstName: "Super",
    lastName: "Admin",
    role: "superAdmin",
    gymId: undefined,
  },
};

describe("Report Generation API", () => {
  describe("Access Control", () => {
    it("should allow clients to view their own reports", async () => {
      const currentUser = mockUsers.client;
      const targetUserId = mockUsers.client.id;

      // Users can always view their own reports
      const hasAccess = currentUser.id === targetUserId;
      expect(hasAccess).toBe(true);
    });

    it("should allow trainers to view assigned client reports", async () => {
      const currentUser = mockUsers.trainer;
      const targetUserId = mockUsers.client.id;

      // Mock assignment exists
      mockDatabase.getTrainerClientAssignments.mockResolvedValue([
        {
          trainerId: currentUser.id,
          clientId: targetUserId,
          isActive: true,
        },
      ]);

      const assignments = await mockDatabase.getTrainerClientAssignments(
        currentUser.id,
      );
      const assignment = assignments.find(
        (a: any) => a.clientId === targetUserId && a.isActive,
      );
      const hasAccess = !!assignment;

      expect(hasAccess).toBe(true);
    });

    it("should deny trainers access to non-assigned client reports", async () => {
      const currentUser = mockUsers.trainer;
      const targetUserId = "user_other_client_999";

      mockDatabase.getTrainerClientAssignments.mockResolvedValue([]);

      const assignments = await mockDatabase.getTrainerClientAssignments(
        currentUser.id,
      );
      const assignment = assignments.find(
        (a: any) => a.clientId === targetUserId && a.isActive,
      );
      const hasAccess = !!assignment;

      expect(hasAccess).toBe(false);
    });

    it("should allow admins to view clients in their gym", async () => {
      const currentUser = mockUsers.admin;
      const targetUser = { ...mockUsers.client, gymId: "gym_123" };

      mockDatabase.getUserById.mockResolvedValue(targetUser);

      const user = await mockDatabase.getUserById(targetUser.id);
      const hasAccess = user?.gymId === currentUser.gymId;

      expect(hasAccess).toBe(true);
    });

    it("should deny admins access to clients outside their gym", async () => {
      const currentUser = mockUsers.admin;
      const targetUser = { ...mockUsers.client, gymId: "gym_other_999" };

      mockDatabase.getUserById.mockResolvedValue(targetUser);

      const user = await mockDatabase.getUserById(targetUser.id);
      const hasAccess = user?.gymId === currentUser.gymId;

      expect(hasAccess).toBe(false);
    });

    it("should allow superAdmins to view any user", async () => {
      const currentUser = mockUsers.superAdmin;

      // SuperAdmins bypass gym checks
      const hasAccess = currentUser.role === "superAdmin";

      expect(hasAccess).toBe(true);
    });

    it("should deny clients access to other users reports", async () => {
      const currentUser = mockUsers.client;
      const targetUserId = "user_other_123";

      // Only own ID or specific permissions
      const hasAccess = currentUser.id === targetUserId;

      expect(hasAccess).toBe(false);
    });
  });

  describe("Date Range Validation", () => {
    it("should accept valid date format YYYY-MM-DD", () => {
      const validDates = ["2024-01-01", "2024-12-31", "2024-06-15"];

      validDates.forEach((date) => {
        const parsed = Date.parse(date);
        expect(isNaN(parsed)).toBe(false);
      });
    });

    it("should reject invalid date formats", () => {
      const invalidDates = ["01-01-2024", "2024/01/01", "2024-1-1", "invalid"];

      invalidDates.forEach((date) => {
        const parsed = Date.parse(date);
        expect(isNaN(parsed)).toBe(true);
      });
    });

    it("should default to last 30 days if no dates provided", () => {
      const endDate = new Date();
      const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

      const daysDiff = Math.ceil(
        (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24),
      );

      expect(daysDiff).toBe(30);
    });
  });

  describe("Data Aggregation", () => {
    it("should aggregate weekly check-ins correctly", () => {
      const attendanceRecords = [
        {
          checkInTime: new Date("2024-01-02T10:00:00"),
          checkOutTime: new Date("2024-01-02T11:00:00"),
          type: "gym",
        },
        {
          checkInTime: new Date("2024-01-03T14:00:00"),
          checkOutTime: new Date("2024-01-03T15:30:00"),
          type: "gym",
        },
        {
          checkInTime: new Date("2024-01-04T09:00:00"),
          checkOutTime: null, // Still checked in
          type: "class",
        },
      ];

      const totalCheckIns = attendanceRecords.length;
      const completedCheckIns = attendanceRecords.filter(
        (r) => r.checkOutTime,
      ).length;

      expect(totalCheckIns).toBe(3);
      expect(completedCheckIns).toBe(2);
    });

    it("should calculate total time spent correctly", () => {
      const attendanceRecords = [
        {
          checkInTime: new Date("2024-01-02T10:00:00"),
          checkOutTime: new Date("2024-01-02T11:30:00"), // 90 minutes
        },
        {
          checkInTime: new Date("2024-01-03T14:00:00"),
          checkOutTime: new Date("2024-01-03T15:00:00"), // 60 minutes
        },
      ];

      const totalMinutes = attendanceRecords.reduce((sum, record) => {
        if (record.checkOutTime) {
          const duration =
            record.checkOutTime.getTime() - record.checkInTime.getTime();
          return sum + Math.floor(duration / 60000);
        }
        return sum;
      }, 0);

      expect(totalMinutes).toBe(150);
    });

    it("should handle nutrition goal achievement correctly", () => {
      const nutritionRecords = [
        { date: "2024-01-01", totalCalories: 2100, calorieGoal: 2000 },
        { date: "2024-01-02", totalCalories: 1900, calorieGoal: 2000 },
        { date: "2024-01-03", totalCalories: 2200, calorieGoal: 2000 },
      ];

      const daysMetGoal = nutritionRecords.filter((record) => {
        const lowerBound = record.calorieGoal * 0.9;
        const upperBound = record.calorieGoal * 1.1;
        return (
          record.totalCalories >= lowerBound &&
          record.totalCalories <= upperBound
        );
      }).length;

      expect(daysMetGoal).toBe(3); // All within ±10%
    });

    it("should handle hydration goal achievement correctly", () => {
      const hydrationRecords = [
        { date: "2024-01-01", totalWater: 2500, waterGoal: 2000 }, // 125%
        { date: "2024-01-02", totalWater: 1800, waterGoal: 2000 }, // 90%
        { date: "2024-01-03", totalWater: 2000, waterGoal: 2000 }, // 100%
      ];

      const daysMetGoal = hydrationRecords.filter(
        (record) => record.totalWater / record.waterGoal >= 1,
      ).length;

      expect(daysMetGoal).toBe(2); // Days 1 and 3 meet goal
    });
  });

  describe("Report Structure", () => {
    it("should include all required sections", () => {
      const requiredSections = [
        "userId",
        "user",
        "client",
        "fitnessProfile",
        "reportPeriod",
        "weeklyCheckIns",
        "nutrition",
        "hydration",
        "goals",
        "profileHistory",
        "recommendations",
        "generatedAt",
      ];

      // Mock report object
      const report = {
        userId: "user_123",
        user: {},
        client: null,
        fitnessProfile: null,
        reportPeriod: { startDate: "", endDate: "" },
        weeklyCheckIns: [],
        nutrition: {
          dailySummaries: [],
          averageDailyCalories: 0,
          totalDays: 0,
          daysMetGoal: 0,
        },
        hydration: {
          dailySummaries: [],
          averageDailyWater: 0,
          totalDays: 0,
          daysMetGoal: 0,
        },
        goals: {
          active: [],
          completed: [],
          totalActive: 0,
          totalCompleted: 0,
          averageProgress: 0,
        },
        profileHistory: [],
        recommendations: {
          accepted: [],
          rejected: [],
          pending: [],
          totalAccepted: 0,
          totalRejected: 0,
          totalPending: 0,
        },
        generatedAt: new Date(),
      };

      requiredSections.forEach((section) => {
        expect(report).toHaveProperty(section);
      });
    });
  });
});

describe("ISO Week Calculations", () => {
  it("should start week on Monday", () => {
    // Test dates
    const dates = [
      { date: "2024-01-01", expectedMonday: "2024-01-01" }, // Monday
      { date: "2024-01-02", expectedMonday: "2024-01-01" }, // Tuesday
      { date: "2024-01-03", expectedMonday: "2024-01-01" }, // Wednesday
      { date: "2024-01-07", expectedMonday: "2024-01-01" }, // Sunday
      { date: "2024-01-08", expectedMonday: "2024-01-08" }, // Monday (next week)
    ];

    dates.forEach(({ date, expectedMonday }) => {
      const current = new Date(date);
      const day = current.getDay();
      const diff = current.getDate() - day + (day === 0 ? -6 : 1);
      const monday = new Date(current);
      monday.setDate(diff);

      const mondayStr = monday.toISOString().split("T")[0];
      expect(mondayStr).toBe(expectedMonday);
    });
  });

  it("should end week on Sunday", () => {
    const monday = new Date("2024-01-01");
    const sunday = new Date(monday);
    sunday.setDate(sunday.getDate() + 6);

    const sundayStr = sunday.toISOString().split("T")[0];
    expect(sundayStr).toBe("2024-01-07");
  });
});
