// Copyright 2026 Hugo Osvaldo Barrera
//
// SPDX-License-Identifier: EUPL-1.2

use std::sync::Arc;
use std::time::{Duration, Instant};

use anyhow::anyhow;
use futures_util::StreamExt as _;
use log::{debug, error, info, warn};
use tokio::{
    sync::{
        broadcast::{self, Sender},
        mpsc::{self, Receiver},
    },
    time::{sleep, timeout},
};
use vstorage::sync::plan::PlanError;
use vstorage::{
    ErrorKind,
    sync::{Side, execute::Executor, plan::IncrementalPlan, status::StatusDatabase},
    watch::{Event, StorageMonitor},
};

use crate::NamedPair;
use crate::error::Error;

const DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
const DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(2);

pub struct Daemon {
    pub pair: NamedPair,
    pub status: Arc<StatusDatabase>,
    pub events: Receiver<MonitorMsg>,
    pub shutdown: Sender<()>,
}

impl Daemon {
    pub async fn new(pair: NamedPair) -> Result<Daemon, Error> {
        // Open status DB once and keep that handle open.
        let status = match StatusDatabase::open_or_create(&pair.status_path) {
            Ok(status) => Arc::new(status),
            Err(err) => return Err(Error::StatusFatal(err.into())),
        };

        let (event_tx, event_rx) = mpsc::channel::<MonitorMsg>(8);
        let (shutdown_tx, _) = broadcast::channel::<()>(1);

        // Spawn source tasks for storage A.
        if !pair.intervals.0.is_zero() {
            tokio::spawn(interval_task(
                Side::A,
                pair.intervals.0,
                event_tx.clone(),
                shutdown_tx.subscribe(),
            ));
        }
        match pair.inner.storage_a().monitor().await {
            Ok(monitor) => {
                let event_tx = event_tx.clone();
                let shutdown_rx = shutdown_tx.subscribe();
                tokio::spawn(monitor_task(Side::A, monitor, event_tx, shutdown_rx));
            }
            Err(e) if e.kind() == &ErrorKind::Unsupported => {} // No monitor, interval only.
            Err(e) => return Err(Error::Other(e.into())),
        }

        // Spawn source tasks for storage B.
        if !pair.intervals.1.is_zero() {
            tokio::spawn(interval_task(
                Side::B,
                pair.intervals.1,
                event_tx.clone(),
                shutdown_tx.subscribe(),
            ));
        }
        match pair.inner.storage_b().monitor().await {
            Ok(monitor) => {
                let event_tx = event_tx.clone();
                let shutdown_rx = shutdown_tx.subscribe();
                tokio::spawn(monitor_task(Side::B, monitor, event_tx, shutdown_rx));
            }
            Err(e) if e.kind() == &ErrorKind::Unsupported => {} // No monitor, interval only.
            Err(e) => return Err(Error::Other(e.into())),
        }

        drop(event_tx); // Drop original sender so channel closes when all producers finish

        Ok(Daemon {
            pair,
            status,
            events: event_rx,
            shutdown: shutdown_tx,
        })
    }

    /// Sync indefinitely.
    ///
    /// Returns an error if and only if a fatal synchronisation error occurred.
    pub async fn run(mut self) -> Error {
        loop {
            let Some(mut msg) = self.events.recv().await else {
                // This shouldn't happen: all producers exited without sending Fatal.
                error!("No event sources for pair {}; bailing.", self.pair.name);
                return Error::Other(anyhow!("Monitor task died unexpectedly"));
            };

            // Debounce events which arrive in quick succession.
            let mut events = Vec::new();
            let batch_start = Instant::now();
            loop {
                match msg {
                    MonitorMsg::Event(side, event) => {
                        debug!("Monitor for {side:?} yielded event {event:?}");
                        events.push((event, side));
                    }
                    MonitorMsg::Fatal(side) => {
                        warn!("Monitor for {side:?} died; storage is no longer accessible.");
                        let _ = self.shutdown.send(());
                        return Error::Other(anyhow!("Monitor task died unexpectedly"));
                    }
                }

                let Some(remaining) = DEBOUNCE_TIMEOUT.checked_sub(batch_start.elapsed()) else {
                    break;
                };
                let wait = DEBOUNCE_INTERVAL.min(remaining);
                msg = match timeout(wait, self.events.recv()).await {
                    Ok(Some(m)) => m,
                    Ok(None) => {
                        error!("No event sources for pair {}; bailing.", self.pair.name);
                        return Error::Other(anyhow!("Monitor task died unexpectedly"));
                    }
                    Err(_) => break, // Timeout expired.
                };
            }

            if let Err(e) = self.sync_changes(events, Arc::clone(&self.status)).await {
                return e;
            }
        }
    }

    /// Run a single synchronisation pass.
    ///
    /// Returns an error if a fatal error occurred.
    async fn sync_changes(
        &self,
        events: Vec<(Event, Side)>,
        status: Arc<StatusDatabase>,
    ) -> Result<(), Error> {
        debug!("Creating plan for storage pair '{}'.", self.pair.name);
        let plan = match IncrementalPlan::new(self.pair.inner.clone(), Some(status.clone()), events)
            .await
        {
            Ok(plan) => plan,
            Err(PlanError::StatusDb(e)) => {
                return Err(Error::StatusFatal(e.into()));
            }
            Err(err) => {
                error!("Error creating plan for {}: {:?}", self.pair.name, err);
                return Ok(());
            }
        };

        let mut plan_stream = self.pair.apply_conflict_resolution_to_plan(plan);
        let executor = Executor::new(
            Arc::clone(self.pair.inner.storage_a()),
            Arc::clone(self.pair.inner.storage_b()),
            status,
        );
        while let Some(next) = plan_stream.next().await {
            let op = match next {
                Ok(op) => op,
                Err(e) => {
                    error!("Planning error: {e:?}");
                    continue;
                }
            };

            match executor.execute_operation(op).await {
                Ok(Ok(())) => {}
                Ok(Err(e)) => error!("{e:?}"),
                Err(status_err) => return Err(Error::StatusFatal(status_err.into())),
            }
        }
        info!("Sync done for {}.", self.pair.name);
        Ok(())
    }
}

/// Message from a monitor or interval source.
pub enum MonitorMsg {
    /// Event was received from a storage.
    Event(Side, Event),
    /// Monitor died; the storage is no longer accessible.
    Fatal(Side),
}

/// Receive events from a monitor and forward them.
/// Bails when a messages is received via `shutdown_rx`.
pub async fn monitor_task(
    side: Side,
    mut monitor: Box<dyn StorageMonitor>,
    event_tx: mpsc::Sender<MonitorMsg>,
    mut shutdown_rx: broadcast::Receiver<()>,
) {
    loop {
        tokio::select! {
            _ = shutdown_rx.recv() => break,
            event = monitor.next_event() => {
                if let Some(e) = event {
                    if event_tx.send(MonitorMsg::Event(side, e)).await.is_err() {
                        break;
                    }
                } else {
                    let _ = event_tx.send(MonitorMsg::Fatal(side)).await;
                    break;
                }
            }
        }
    }
}

/// Tick on an interval and send [`Event::Storage`].
/// Bails when a messages is received via `shutdown_rx`.
pub async fn interval_task(
    side: Side,
    interval: Duration,
    event_tx: mpsc::Sender<MonitorMsg>,
    mut shutdown_rx: broadcast::Receiver<()>,
) {
    loop {
        tokio::select! {
            _ = shutdown_rx.recv() => break,
            () = sleep(interval) => {
                if event_tx.send(MonitorMsg::Event(side, Event::Storage)).await.is_err() {
                    break;
                }
            }
        }
    }
}
