package vassistent.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import vassistent.model.DatabaseEntry;
import vassistent.model.RatioPoint;

import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

/**
 * Unit and integration tests for {@link StatisticsService}.
 */
@ExtendWith(MockitoExtension.class)
class StatisticsServiceTest {

    @TempDir
    Path tempDir;

    @Mock
    private DataPersistenceService mockedPersistenceService;

    private StatisticsService unitService;

    /**
     * Creates unit-level service instance with mocked persistence dependency.
     */
    @BeforeEach
    void setUp() {
        unitService = new StatisticsService(mockedPersistenceService);
    }

    /**
     * Verifies rolling average output for a deterministic input sequence.
     */
    @Test
    void getLastNAverages_computesExpectedRollingValues() {
        LocalDateTime t0 = LocalDateTime.now();
        List<DatabaseEntry> entries = List.of(
                new DatabaseEntry(t0, 1),
                new DatabaseEntry(t0.plusSeconds(1), 0),
                new DatabaseEntry(t0.plusSeconds(2), 1)
        );
        when(mockedPersistenceService.getLastEntries(3)).thenReturn(entries);

        List<RatioPoint> result = unitService.getLastNAverages(2);

        assertEquals(3, result.size());
        assertEquals(1.0, result.get(0).getRatio(), 0.0001);
        assertEquals(0.5, result.get(1).getRatio(), 0.0001);
        assertEquals(0.5, result.get(2).getRatio(), 0.0001);
    }

    /**
     * Verifies that empty persistence result yields an empty chart result.
     */
    @Test
    void getLastNAverages_withNoEntries_returnsEmptyList() {
        when(mockedPersistenceService.getLastEntries(6)).thenReturn(List.of());

        List<RatioPoint> result = unitService.getLastNAverages(5);

        assertTrue(result.isEmpty());
    }

    /**
     * Verifies that non-positive window sizes yield an empty result.
     */
    @Test
    void getLastNAverages_withNonPositiveWindow_returnsEmptyList() {
        List<RatioPoint> result = unitService.getLastNAverages(0);

        assertTrue(result.isEmpty());
    }

    /**
     * Verifies average ratio over newest values using real SQLite persistence.
     *
     * @throws InterruptedException if sleeps used to separate timestamps are interrupted
     */
    @Test
    void getRatio_returnsAverageOfLastN() throws InterruptedException {
        String dbUrl = "jdbc:sqlite:" + tempDir.resolve("ratio-test.db").toAbsolutePath();
        DataPersistenceService integrationPersistence = new DataPersistenceService(dbUrl);
        StatisticsService integrationService = new StatisticsService(integrationPersistence);

        integrationPersistence.store(1);
        Thread.sleep(5);
        integrationPersistence.store(0);
        Thread.sleep(5);
        integrationPersistence.store(1);

        double ratio = integrationService.getRatio(3);

        assertEquals(2.0 / 3.0, ratio, 0.0001);
    }

    /**
     * Verifies that requesting a ratio on an empty dataset returns zero.
     */
    @Test
    void getRatio_withNoData_returnsZero() {
        String dbUrl = "jdbc:sqlite:" + tempDir.resolve("ratio-empty.db").toAbsolutePath();
        DataPersistenceService integrationPersistence = new DataPersistenceService(dbUrl);
        StatisticsService integrationService = new StatisticsService(integrationPersistence);

        double ratio = integrationService.getRatio(20);

        assertEquals(0.0, ratio, 0.0001);
    }

    /**
     * Verifies that the newest rolling average matches {@link StatisticsService#getRatio(int)}
     * for the same window size.
     */
    @Test
    void getLastNAverages_lastPointMatchesGetRatio() {
        String dbUrl = "jdbc:sqlite:" + tempDir.resolve("ratio-rolling.db").toAbsolutePath();
        DataPersistenceService integrationPersistence = new DataPersistenceService(dbUrl);
        StatisticsService integrationService = new StatisticsService(integrationPersistence);

        integrationPersistence.store(1);
        integrationPersistence.store(0);
        integrationPersistence.store(1);
        integrationPersistence.store(1);

        int window = 3;

        List<RatioPoint> rolling = integrationService.getLastNAverages(window);
        double ratio = integrationService.getRatio(window);

        assertFalse(rolling.isEmpty());
        assertEquals(ratio, rolling.get(rolling.size() - 1).getRatio(), 0.0001);
    }
}
