import { MeasurementCategory, MeasurementEntry } from "@/components/Measurements";
import { WeightEntry } from "@/components/Weight";
import { getMeasurementCategories } from "@/components/Measurements/api/measurements";
import { getNutritionalDiaryEntries } from "@/components/Nutrition/api/nutritionalDiary";
import { getSessions } from "@/components/Routines/api/session";
import { getWeights } from "@/components/Weight/api/weight";
import { TEST_DIARY_ENTRY_1, TEST_DIARY_ENTRY_2 } from "@/tests/nutritionDiaryTestdata";
import { testQueryClient } from "@/tests/queryClient";
import { testWorkoutSession } from "@/tests/workoutLogsRoutinesTestData";
import { dateToYYYYMMDD } from "@/core/lib/date";
import { QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import i18n from "i18next";
import React from "react";
import { I18nextProvider } from "react-i18next";
import { BrowserRouter } from "react-router-dom";
import type { Mock } from 'vitest';
import CalendarComponent from "./CalendarComponent";

vi.mock("@/components/Measurements/api/measurements");
vi.mock("@/components/Nutrition/api/nutritionalDiary");
vi.mock("@/components/Routines/api/session");
vi.mock("@/components/Weight/api/weight");


// TODO: using vi.useFakeTimers() and vi.setSystemTime(new Date('2024-12-01'));
//       seems to break the test and they never complete. As a workaround the dates
//       for the entries are set to the 1st and 2nd of the month, but that means
//       that this test won't work on the 1st of every month since days in the future
//       are not clickable.
describe('CalendarComponent', () => {
    const today = new Date();
    const currentYear = today.getFullYear();
    const currentMonth = today.getMonth();

    const user = userEvent.setup();

    beforeEach(() => {

        (getWeights as Mock).mockImplementation(() => Promise.resolve([
            new WeightEntry(
                new Date(currentYear, currentMonth, 2, 12, 0),
                70
            ),
        ]));

        (getSessions as Mock).mockImplementation(() => Promise.resolve(
            [testWorkoutSession]
        ));

        (getMeasurementCategories as Mock).mockImplementation(() => Promise.resolve([
            new MeasurementCategory(
                'cccccccc-cccc-cccc-cccc-000000000001',
                "Body Fat",
                "%",
                [new MeasurementEntry(
                    'dddddddd-dddd-dddd-dddd-000000000001',
                    'cccccccc-cccc-cccc-cccc-000000000001',
                    new Date(currentYear, currentMonth, 1, 12, 0), 20, "Normal"
                )]
            ),
        ]));

        (getNutritionalDiaryEntries as Mock).mockImplementation(() => Promise.resolve([
            TEST_DIARY_ENTRY_1,
            TEST_DIARY_ENTRY_2,
        ]));

        testQueryClient.clear();
    });

    afterEach(() => {
        vi.clearAllMocks();
    });

    const renderComponent = () => {
        render(
            <BrowserRouter>
                <I18nextProvider i18n={i18n}>
                    <QueryClientProvider client={testQueryClient}>
                        <CalendarComponent />
                    </QueryClientProvider>
                </I18nextProvider>
            </BrowserRouter>
        );
    };

    const getDaysInMonth = (year: number, month: number) => {
        return new Date(year, month + 1, 0).getDate();
    };

    test("renders calendar with days and header", () => {
        renderComponent();
        const days = screen.getAllByText(/^\d+$/);
        const monthName = new Date(currentYear, currentMonth, 1).toLocaleString('en-US', { month: 'long' });
        expect(days.length).toBeGreaterThan(getDaysInMonth(currentYear, currentMonth));

        expect(screen.getByText(`${monthName} ${currentYear}`)).toBeInTheDocument();

        const weekDays = Array.from({ length: 7 }, (_, i) =>
            new Date(1970, 0, i + 5).toLocaleString('en-US', { weekday: 'short' })
        );
        weekDays.forEach((day) => {
            expect(screen.getByText(day)).toBeInTheDocument();
        });
    });

    test('navigates to previous and next month', () => {
        renderComponent();

        const previousMonthDate = new Date(currentYear, currentMonth - 1, 1);
        const previousMonthName = previousMonthDate.toLocaleString('en-US', { month: 'long' });
        const previousMonthYear = previousMonthDate.getFullYear();

        fireEvent.click(screen.getByText('<'));
        expect(screen.getByText(`${previousMonthName} ${previousMonthYear}`)).toBeInTheDocument();

        fireEvent.click(screen.getByText('>'));
        const currentMonthName = today.toLocaleString('en-US', { month: 'long' });
        expect(screen.getByText(`${currentMonthName} ${currentYear}`)).toBeInTheDocument();
    });

    test('displays measurement details for selected day', async () => {
        // Arrange
        renderComponent();

        // Act
        const day = await screen.findByTestId(`day-${dateToYYYYMMDD(new Date(currentYear, currentMonth, 1))}`);
        await user.click(day);

        // Assert
        expect(await screen.findByText(/body fat: 20 %/i)).toBeInTheDocument();
    });

    test('displays weight details for selected day', async () => {
        // Arrange
        renderComponent();

        // Act
        const day = screen.getByTestId(`day-${dateToYYYYMMDD(new Date(currentYear, currentMonth, 2))}`);
        await user.click(day);

        // Assert
        expect(screen.getByText('70.0')).toBeInTheDocument();
    });
});