package auctiora;

import org.junit.jupiter.api.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.time.LocalDateTime;

import static org.junit.jupiter.api.Assertions.*;

/**
 * Test cases for TroostwijkMonitor.
 * Tests monitoring orchestration, bid tracking, and notification triggers.
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TroostwijkMonitorTest {

    private String testDbPath;
    private TroostwijkMonitor monitor;

    @BeforeAll
    void setUp() throws SQLException, IOException {
        testDbPath = "test_monitor_" + System.currentTimeMillis() + ".db";

        // Initialize with non-existent YOLO models (disabled mode)
        monitor = new TroostwijkMonitor(
            testDbPath,
            "desktop",
            "non_existent.cfg",
            "non_existent.weights",
            "non_existent.txt"
        );
    }

    @AfterAll
    void tearDown() throws Exception {
        Files.deleteIfExists(Paths.get(testDbPath));
    }

    @Test
    @DisplayName("Should initialize monitor successfully")
    void testMonitorInitialization() {
        assertNotNull(monitor);
        assertNotNull(monitor.getDb());
    }

    @Test
    @DisplayName("Should print database stats without error")
    void testPrintDatabaseStats() {
        assertDoesNotThrow(() -> monitor.printDatabaseStats());
    }

    @Test
    @DisplayName("Should process pending images without error")
    void testProcessPendingImages() {
        assertDoesNotThrow(() -> monitor.processPendingImages());
    }

    @Test
    @DisplayName("Should handle empty database gracefully")
    void testEmptyDatabaseHandling() throws SQLException {
        var auctions = monitor.getDb().getAllAuctions();
        var lots = monitor.getDb().getAllLots();

        assertNotNull(auctions);
        assertNotNull(lots);
        assertTrue(auctions.isEmpty() || auctions.size() >= 0);
    }

    @Test
    @DisplayName("Should track lots in database")
    void testLotTracking() throws SQLException {
        // Insert test lot
        var lot = new Lot(
            11111, 22222,
            "Test Forklift",
            "Electric forklift in good condition",
            "Toyota",
            "Electric",
            2020,
            "Machinery",
            1500.00,
            "EUR",
            "https://example.com/lot/22222",
            LocalDateTime.now().plusDays(1),
            false
        );

        monitor.getDb().upsertLot(lot);

        var lots = monitor.getDb().getAllLots();
        assertTrue(lots.stream().anyMatch(l -> l.lotId() == 22222));
    }

    @Test
    @DisplayName("Should monitor lots closing soon")
    void testClosingSoonMonitoring() throws SQLException {
        // Insert lot closing in 4 minutes
        var closingSoon = new Lot(
            33333, 44444,
            "Closing Soon Item",
            "Description",
            "",
            "",
            0,
            "Category",
            100.00,
            "EUR",
            "https://example.com/lot/44444",
            LocalDateTime.now().plusMinutes(4),
            false
        );

        monitor.getDb().upsertLot(closingSoon);

        var lots = monitor.getDb().getActiveLots();
        var found = lots.stream()
            .filter(l -> l.lotId() == 44444)
            .findFirst()
            .orElse(null);

        assertNotNull(found);
        assertTrue(found.minutesUntilClose() < 30);
    }

    @Test
    @DisplayName("Should identify lots with time remaining")
    void testTimeRemainingCalculation() throws SQLException {
        var futureLot = new Lot(
            55555, 66666,
            "Future Lot",
            "Description",
            "",
            "",
            0,
            "Category",
            200.00,
            "EUR",
            "https://example.com/lot/66666",
            LocalDateTime.now().plusHours(2),
            false
        );

        monitor.getDb().upsertLot(futureLot);

        var lots = monitor.getDb().getActiveLots();
        var found = lots.stream()
            .filter(l -> l.lotId() == 66666)
            .findFirst()
            .orElse(null);

        assertNotNull(found);
        assertTrue(found.minutesUntilClose() > 60);
    }

    @Test
    @DisplayName("Should handle lots without closing time")
    void testLotsWithoutClosingTime() throws SQLException {
        var noClosing = new Lot(
            77777, 88888,
            "No Closing Time",
            "Description",
            "",
            "",
            0,
            "Category",
            150.00,
            "EUR",
            "https://example.com/lot/88888",
            null,
            false
        );

        monitor.getDb().upsertLot(noClosing);

        var lots = monitor.getDb().getActiveLots();
        var found = lots.stream()
            .filter(l -> l.lotId() == 88888)
            .findFirst()
            .orElse(null);

        assertNotNull(found);
        assertNull(found.closingTime());
    }

    @Test
    @DisplayName("Should track notification status")
    void testNotificationStatusTracking() throws SQLException {
        var lot = new Lot(
            99999, 11110,
            "Test Notification",
            "Description",
            "",
            "",
            0,
            "Category",
            100.00,
            "EUR",
            "https://example.com/lot/11110",
            LocalDateTime.now().plusMinutes(3),
            false
        );

        monitor.getDb().upsertLot(lot);

        // Update notification flag
        var notified = new Lot(
            99999, 11110,
            "Test Notification",
            "Description",
            "",
            "",
            0,
            "Category",
            100.00,
            "EUR",
            "https://example.com/lot/11110",
            LocalDateTime.now().plusMinutes(3),
            true
        );

        monitor.getDb().updateLotNotificationFlags(notified);

        var lots = monitor.getDb().getActiveLots();
        var found = lots.stream()
            .filter(l -> l.lotId() == 11110)
            .findFirst()
            .orElse(null);

        assertNotNull(found);
        assertTrue(found.closingNotified());
    }

    @Test
    @DisplayName("Should update bid amounts")
    void testBidAmountUpdates() throws SQLException {
        var lot = new Lot(
            12121, 13131,
            "Bid Update Test",
            "Description",
            "",
            "",
            0,
            "Category",
            100.00,
            "EUR",
            "https://example.com/lot/13131",
            LocalDateTime.now().plusDays(1),
            false
        );

        monitor.getDb().upsertLot(lot);

        // Simulate bid increase
        var higherBid = new Lot(
            12121, 13131,
            "Bid Update Test",
            "Description",
            "",
            "",
            0,
            "Category",
            250.00,
            "EUR",
            "https://example.com/lot/13131",
            LocalDateTime.now().plusDays(1),
            false
        );

        monitor.getDb().updateLotCurrentBid(higherBid);

        var lots = monitor.getDb().getActiveLots();
        var found = lots.stream()
            .filter(l -> l.lotId() == 13131)
            .findFirst()
            .orElse(null);

        assertNotNull(found);
        assertEquals(250.00, found.currentBid(), 0.01);
    }

    @Test
    @DisplayName("Should handle multiple concurrent lot updates")
    void testConcurrentLotUpdates() throws InterruptedException, SQLException {
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    monitor.getDb().upsertLot(new Lot(
                        20000 + i, 30000 + i, "Concurrent " + i, "Desc", "", "", 0, "Cat",
                        100.0, "EUR", "https://example.com/" + i, null, false
                    ));
                }
            } catch (SQLException e) {
                fail("Thread 1 failed: " + e.getMessage());
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                for (int i = 5; i < 10; i++) {
                    monitor.getDb().upsertLot(new Lot(
                        20000 + i, 30000 + i, "Concurrent " + i, "Desc", "", "", 0, "Cat",
                        200.0, "EUR", "https://example.com/" + i, null, false
                    ));
                }
            } catch (SQLException e) {
                fail("Thread 2 failed: " + e.getMessage());
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        var lots = monitor.getDb().getActiveLots();
        long count = lots.stream()
            .filter(l -> l.lotId() >= 30000 && l.lotId() < 30010)
            .count();

        assertTrue(count >= 10);
    }

    @Test
    @DisplayName("Should schedule monitoring without error")
    void testScheduleMonitoring() {
        // This just tests that scheduling doesn't throw
        // Actual monitoring would run in background
        assertDoesNotThrow(() -> {
            // Don't actually start monitoring in test
            // Just verify monitor is ready
            assertNotNull(monitor);
        });
    }

    @Test
    @DisplayName("Should handle database with auctions and lots")
    void testDatabaseWithData() throws SQLException {
        // Insert auction
        var auction = new AuctionInfo(
            40000,
            "Test Auction",
            "Amsterdam, NL",
            "Amsterdam",
            "NL",
            "https://example.com/auction/40000",
            "A7",
            10,
            LocalDateTime.now().plusDays(2)
        );

        monitor.getDb().upsertAuction(auction);

        // Insert related lot
        var lot = new Lot(
            40000, 50000,
            "Test Lot",
            "Description",
            "",
            "",
            0,
            "Category",
            500.00,
            "EUR",
            "https://example.com/lot/50000",
            LocalDateTime.now().plusDays(2),
            false
        );

        monitor.getDb().upsertLot(lot);

        // Verify
        var auctions = monitor.getDb().getAllAuctions();
        var lots = monitor.getDb().getAllLots();

        assertTrue(auctions.stream().anyMatch(a -> a.auctionId() == 40000));
        assertTrue(lots.stream().anyMatch(l -> l.lotId() == 50000));
    }
}
