package auctiora;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.sql.SQLException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Slf4j
public class TroostwijkMonitor {
   
   private static final String LOT_API = "https://api.troostwijkauctions.com/lot/7/list";
   
   RateLimitedHttpClient httpClient;
   ObjectMapper          objectMapper;
   @Getter DatabaseService db;
   NotificationService    notifier;
   ObjectDetectionService detector;
   ImageProcessingService imageProcessor;
   
   ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
      var t = new Thread(r, "troostwijk-monitor-thread");
      t.setDaemon(true);
      return t;
   });
   
   public TroostwijkMonitor(String databasePath,
                            String notificationConfig,
                            String yoloCfgPath,
                            String yoloWeightsPath,
                            String classNamesPath)
   throws SQLException, IOException {
      
      httpClient     = new RateLimitedHttpClient();
      objectMapper   = new ObjectMapper();
      db             = new DatabaseService(databasePath);
      notifier       = new NotificationService(notificationConfig);
      detector       = new ObjectDetectionService(yoloCfgPath, yoloWeightsPath, classNamesPath);
      imageProcessor = new ImageProcessingService(db, detector, httpClient);
      
      db.ensureSchema();
   }
   
   public void scheduleMonitoring() {
      scheduler.scheduleAtFixedRate(this::monitorAllLots, 0, 1, TimeUnit.HOURS);
      log.info("✓ Monitoring service started");
   }
   
   private void monitorAllLots() {
      try {
         var activeLots = db.getActiveLots();
         log.info("Monitoring {} active lots …", activeLots.size());
         for (var lot : activeLots) {
            checkAndUpdateLot(lot);
         }
      } catch (SQLException e) {
         log.error("Error during scheduled monitoring", e);
      }
   }
   
   private void checkAndUpdateLot(Lot lot) {
      refreshLotBid(lot);
      
      var minutesLeft = lot.minutesUntilClose();
      if (minutesLeft < 30) {
         if (minutesLeft <= 5 && !lot.closingNotified()) {
            notifier.sendNotification(
                    "Kavel " + lot.lotId() + " sluit binnen " + minutesLeft + " min.",
                    "Lot nearing closure", 1);
            try {
               db.updateLotNotificationFlags(lot.withClosingNotified(true));
            } catch (SQLException e) {
               throw new RuntimeException(e);
            }
         }
         scheduler.schedule(() -> refreshLotBid(lot), 5, TimeUnit.MINUTES);
      }
   }
   
   private void refreshLotBid(Lot lot) {
      try {
         var url = LOT_API +
                   "?batchSize=1&listType=7&offset=0&sortOption=0" +
                   "&saleID=" + lot.saleId() +
                   "&parentID=0&relationID=0&buildversion=201807311" +
                   "&lotID=" + lot.lotId();
         
         var resp = httpClient.sendGet(url);
         if (resp.statusCode() != 200) return;
         
         var root    = objectMapper.readTree(resp.body());
         var results = root.path("results");
         if (results.isArray() && results.size() > 0) {
            var newBid = results.get(0).path("cb").asDouble();
            if (Double.compare(newBid, lot.currentBid()) > 0) {
               var previous   = lot.currentBid();
               var updatedLot = lot.withCurrentBid(newBid);
               db.updateLotCurrentBid(updatedLot);
               var msg = String.format(
                       "Nieuw bod op kavel %d: €%.2f (was €%.2f)",
                       lot.lotId(), newBid, previous);
               notifier.sendNotification(msg, "Kavel bieding update", 0);
            }
         }
      } catch (IOException | InterruptedException | SQLException e) {
         log.warn("Failed to refresh bid for lot {}", lot.lotId(), e);
         if (e instanceof InterruptedException) Thread.currentThread().interrupt();
      }
   }
   
   public void printDatabaseStats() {
      try {
         var allLots    = db.getAllLots();
         var imageCount = db.getImageCount();
         log.info("📊 Database Summary: total lots = {}, total images = {}",
                  allLots.size(), imageCount);
         if (!allLots.isEmpty()) {
            var sum = allLots.stream().mapToDouble(Lot::currentBid).sum();
            log.info("Total current bids: €{:.2f}", sum);
         }
      } catch (SQLException e) {
         log.warn("Could not retrieve database stats", e);
      }
   }
   
   public void processPendingImages() {
      imageProcessor.processPendingImages();
   }
}
