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 java.util.List;

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

/**
 * Integration test for complete workflow.
 * Tests end-to-end scenarios including:
 * 1. Scraper data import
 * 2. Data transformation
 * 3. Image processing
 * 4. Object detection
 * 5. Bid monitoring
 * 6. Notifications
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class IntegrationTest {

    private String testDbPath;
    private DatabaseService db;
    private NotificationService notifier;
    private ObjectDetectionService detector;
    private ImageProcessingService imageProcessor;
    private TroostwijkMonitor monitor;

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

        // Initialize all services
        db = new DatabaseService(testDbPath);
        db.ensureSchema();

        notifier = new NotificationService("desktop");

        detector = new ObjectDetectionService(
            "non_existent.cfg",
            "non_existent.weights",
            "non_existent.txt"
        );

        RateLimitedHttpClient2 httpClient = new RateLimitedHttpClient2();
        imageProcessor = new ImageProcessingService(db, detector, httpClient);

        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
    @Order(1)
    @DisplayName("Integration: Complete scraper data import workflow")
    void testCompleteScraperImportWorkflow() throws SQLException {
        // Step 1: Import auction from scraper format
        var auction = new AuctionInfo(
            12345,
            "Industrial Equipment Auction",
            "Rotterdam, NL",
            "Rotterdam",
            "NL",
            "https://example.com/auction/12345",
            "A7",
            25,
            LocalDateTime.now().plusDays(3)
        );

        db.upsertAuction(auction);

        // Step 2: Import lots for this auction
        var lot1 = new Lot(
            12345, 10001,
            "Toyota Forklift 2.5T",
            "Electric forklift in excellent condition",
            "Toyota",
            "Electric",
            2018,
            "Machinery",
            1500.00,
            "EUR",
            "https://example.com/lot/10001",
            LocalDateTime.now().plusDays(3),
            false
        );

        var lot2 = new Lot(
            12345, 10002,
            "Office Furniture Set",
            "Desks, chairs, and cabinets",
            "",
            "",
            0,
            "Furniture",
            500.00,
            "EUR",
            "https://example.com/lot/10002",
            LocalDateTime.now().plusDays(3),
            false
        );

        db.upsertLot(lot1);
        db.upsertLot(lot2);

        // Verify import
        var auctions = db.getAllAuctions();
        var lots = db.getAllLots();

        assertTrue(auctions.stream().anyMatch(a -> a.auctionId() == 12345));
        assertEquals(2, lots.stream().filter(l -> l.saleId() == 12345).count());
    }

    @Test
    @Order(2)
    @DisplayName("Integration: Image processing and detection workflow")
    void testImageProcessingWorkflow() throws SQLException {
        // Add images for a lot
        db.insertImage(10001, "https://example.com/img1.jpg",
            "C:/images/10001/img1.jpg", List.of("truck", "vehicle"));

        db.insertImage(10001, "https://example.com/img2.jpg",
            "C:/images/10001/img2.jpg", List.of("forklift", "machinery"));

        // Verify images were saved
        var images = db.getImagesForLot(10001);
        assertEquals(2, images.size());

        var labels = images.stream()
            .flatMap(img -> List.of(img.labels().split(",")).stream())
            .distinct()
            .toList();

        assertTrue(labels.contains("truck") || labels.contains("forklift"));
    }

    @Test
    @Order(3)
    @DisplayName("Integration: Bid monitoring and notification workflow")
    void testBidMonitoringWorkflow() throws SQLException {
        // Simulate bid change
        var lot = db.getAllLots().stream()
            .filter(l -> l.lotId() == 10001)
            .findFirst()
            .orElseThrow();

        // Update bid
        var updatedLot = new Lot(
            lot.saleId(), lot.lotId(), lot.title(), lot.description(),
            lot.manufacturer(), lot.type(), lot.year(), lot.category(),
            2000.00, // Increased from 1500.00
            lot.currency(), lot.url(), lot.closingTime(), lot.closingNotified()
        );

        db.updateLotCurrentBid(updatedLot);

        // Send notification
        String message = String.format("Nieuw bod op kavel %d: €%.2f (was €%.2f)",
            lot.lotId(), 2000.00, 1500.00);

        assertDoesNotThrow(() ->
            notifier.sendNotification(message, "Kavel bieding update", 0)
        );

        // Verify bid was updated
        var refreshed = db.getAllLots().stream()
            .filter(l -> l.lotId() == 10001)
            .findFirst()
            .orElseThrow();

        assertEquals(2000.00, refreshed.currentBid(), 0.01);
    }

    @Test
    @Order(4)
    @DisplayName("Integration: Closing alert workflow")
    void testClosingAlertWorkflow() throws SQLException {
        // Create lot closing soon
        var closingSoon = new Lot(
            12345, 20001,
            "Closing Soon Item",
            "Description",
            "",
            "",
            0,
            "Category",
            750.00,
            "EUR",
            "https://example.com/lot/20001",
            LocalDateTime.now().plusMinutes(4),
            false
        );

        db.upsertLot(closingSoon);

        // Check if lot is closing soon
        assertTrue(closingSoon.minutesUntilClose() < 5);

        // Send high-priority notification
        String message = "Kavel " + closingSoon.lotId() + " sluit binnen 5 min.";
        assertDoesNotThrow(() ->
            notifier.sendNotification(message, "Lot nearing closure", 1)
        );

        // Mark as notified
        var notified = new Lot(
            closingSoon.saleId(), closingSoon.lotId(), closingSoon.title(),
            closingSoon.description(), closingSoon.manufacturer(), closingSoon.type(),
            closingSoon.year(), closingSoon.category(), closingSoon.currentBid(),
            closingSoon.currency(), closingSoon.url(), closingSoon.closingTime(),
            true
        );

        db.updateLotNotificationFlags(notified);

        // Verify notification flag
        var updated = db.getAllLots().stream()
            .filter(l -> l.lotId() == 20001)
            .findFirst()
            .orElseThrow();

        assertTrue(updated.closingNotified());
    }

    @Test
    @Order(5)
    @DisplayName("Integration: Multi-country auction filtering")
    void testMultiCountryFiltering() throws SQLException {
        // Add auctions from different countries
        db.upsertAuction(new AuctionInfo(
            30001, "Dutch Auction", "Amsterdam, NL", "Amsterdam", "NL",
            "https://example.com/30001", "A1", 10, null
        ));

        db.upsertAuction(new AuctionInfo(
            30002, "Romanian Auction", "Cluj, RO", "Cluj", "RO",
            "https://example.com/30002", "A2", 15, null
        ));

        db.upsertAuction(new AuctionInfo(
            30003, "Belgian Auction", "Brussels, BE", "Brussels", "BE",
            "https://example.com/30003", "A3", 20, null
        ));

        // Filter by country
        var nlAuctions = db.getAuctionsByCountry("NL");
        var roAuctions = db.getAuctionsByCountry("RO");
        var beAuctions = db.getAuctionsByCountry("BE");

        assertTrue(nlAuctions.stream().anyMatch(a -> a.auctionId() == 30001));
        assertTrue(roAuctions.stream().anyMatch(a -> a.auctionId() == 30002));
        assertTrue(beAuctions.stream().anyMatch(a -> a.auctionId() == 30003));
    }

    @Test
    @Order(6)
    @DisplayName("Integration: Complete monitoring cycle")
    void testCompleteMonitoringCycle() throws SQLException {
        // Monitor should handle all lots
        monitor.printDatabaseStats();

        var activeLots = db.getActiveLots();
        assertFalse(activeLots.isEmpty());

        // Process pending images
        assertDoesNotThrow(() -> monitor.processPendingImages());

        // Verify database integrity
        int imageCount = db.getImageCount();
        assertTrue(imageCount >= 0);
    }

    @Test
    @Order(7)
    @DisplayName("Integration: Data consistency across services")
    void testDataConsistency() throws SQLException {
        // Verify all auctions have valid data
        var auctions = db.getAllAuctions();
        for (var auction : auctions) {
            assertNotNull(auction.auctionId());
            assertNotNull(auction.title());
            assertNotNull(auction.url());
        }

        // Verify all lots have valid data
        var lots = db.getAllLots();
        for (var lot : lots) {
            assertNotNull(lot.lotId());
            assertNotNull(lot.title());
            assertTrue(lot.currentBid() >= 0);
        }
    }

    @Test
    @Order(8)
    @DisplayName("Integration: Object detection value estimation workflow")
    void testValueEstimationWorkflow() throws SQLException {
        // Create lot with detected objects
        var lot = new Lot(
            40000, 50000,
            "Construction Equipment",
            "Heavy machinery for construction",
            "Caterpillar",
            "Excavator",
            2015,
            "Machinery",
            25000.00,
            "EUR",
            "https://example.com/lot/50000",
            LocalDateTime.now().plusDays(5),
            false
        );

        db.upsertLot(lot);

        // Add images with detected objects
        db.insertImage(50000, "https://example.com/excavator1.jpg",
            "C:/images/50000/1.jpg", List.of("truck", "excavator", "machinery"));

        db.insertImage(50000, "https://example.com/excavator2.jpg",
            "C:/images/50000/2.jpg", List.of("excavator", "construction"));

        // Retrieve and analyze
        var images = db.getImagesForLot(50000);
        assertFalse(images.isEmpty());

        // Count unique objects
        var allLabels = images.stream()
            .flatMap(img -> List.of(img.labels().split(",")).stream())
            .distinct()
            .toList();

        assertTrue(allLabels.contains("excavator") || allLabels.contains("machinery"));

        // Simulate value estimation notification
        String message = String.format(
            "Lot contains: %s\nEstimated value: €%,.2f",
            String.join(", ", allLabels),
            lot.currentBid()
        );

        assertDoesNotThrow(() ->
            notifier.sendNotification(message, "Object Detected", 0)
        );
    }

    @Test
    @Order(9)
    @DisplayName("Integration: Handle rapid concurrent updates")
    void testConcurrentOperations() throws InterruptedException, SQLException {
        Thread auctionThread = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    db.upsertAuction(new AuctionInfo(
                        60000 + i, "Concurrent Auction " + i, "Test, NL", "Test", "NL",
                        "https://example.com/60" + i, "A1", 5, null
                    ));
                }
            } catch (SQLException e) {
                fail("Auction thread failed: " + e.getMessage());
            }
        });

        Thread lotThread = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    db.upsertLot(new Lot(
                        60000 + i, 70000 + i, "Concurrent Lot " + i, "Desc", "", "", 0, "Cat",
                        100.0 * i, "EUR", "https://example.com/70" + i, null, false
                    ));
                }
            } catch (SQLException e) {
                fail("Lot thread failed: " + e.getMessage());
            }
        });

        auctionThread.start();
        lotThread.start();
        auctionThread.join();
        lotThread.join();

        // Verify all were inserted
        var auctions = db.getAllAuctions();
        var lots = db.getAllLots();

        long auctionCount = auctions.stream()
            .filter(a -> a.auctionId() >= 60000 && a.auctionId() < 60010)
            .count();

        long lotCount = lots.stream()
            .filter(l -> l.lotId() >= 70000 && l.lotId() < 70010)
            .count();

        assertEquals(10, auctionCount);
        assertEquals(10, lotCount);
    }

    @Test
    @Order(10)
    @DisplayName("Integration: End-to-end notification scenarios")
    void testAllNotificationScenarios() {
        // 1. Bid change notification
        assertDoesNotThrow(() ->
            notifier.sendNotification(
                "Nieuw bod op kavel 12345: €150.00 (was €125.00)",
                "Kavel bieding update",
                0
            )
        );

        // 2. Closing alert
        assertDoesNotThrow(() ->
            notifier.sendNotification(
                "Kavel 67890 sluit binnen 5 min.",
                "Lot nearing closure",
                1
            )
        );

        // 3. Object detection
        assertDoesNotThrow(() ->
            notifier.sendNotification(
                "Detected: car, truck, machinery",
                "Object Detected",
                0
            )
        );

        // 4. Value estimate
        assertDoesNotThrow(() ->
            notifier.sendNotification(
                "Geschatte waarde: €5,000 - €7,500",
                "Value Estimate",
                0
            )
        );

        // 5. Viewing day reminder
        assertDoesNotThrow(() ->
            notifier.sendNotification(
                "Bezichtiging op 15-12-2025 om 14:00",
                "Viewing Day Reminder",
                0
            )
        );
    }
}
