//! Sync service.

use std::sync::Arc;

use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info, warn};

use synor_network::{NetworkEvent, SyncState as NetworkSyncState, SyncStatus as NetworkSyncStatus};

use crate::config::NodeConfig;
use crate::services::{ConsensusService, NetworkService, StorageService};

/// Sync state.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SyncState {
    /// Initial state, waiting to start.
    Idle,
    /// Finding peers to sync from.
    FindingPeers,
    /// Downloading headers.
    Headers,
    /// Downloading blocks.
    Blocks,
    /// Processing downloaded data.
    Processing,
    /// Downloading UTXO set (for pruned sync).
    UtxoSet,
    /// Synced, following the chain.
    Synced,
}

impl From<NetworkSyncState> for SyncState {
    fn from(state: NetworkSyncState) -> Self {
        match state {
            NetworkSyncState::Idle => SyncState::Idle,
            NetworkSyncState::FindingPeers => SyncState::FindingPeers,
            NetworkSyncState::DownloadingHeaders => SyncState::Headers,
            NetworkSyncState::DownloadingBlocks => SyncState::Blocks,
            NetworkSyncState::Processing => SyncState::Processing,
            NetworkSyncState::Synced => SyncState::Synced,
        }
    }
}

/// Sync progress.
#[derive(Clone, Debug)]
pub struct SyncProgress {
    /// Current state.
    pub state: SyncState,
    /// Current blue score.
    pub current_blue_score: u64,
    /// Target blue score.
    pub target_blue_score: u64,
    /// Sync percentage.
    pub progress: f64,
    /// Headers downloaded.
    pub headers_downloaded: u64,
    /// Blocks downloaded.
    pub blocks_downloaded: u64,
    /// Blocks per second.
    pub blocks_per_second: f64,
    /// Estimated time remaining in seconds.
    pub eta_seconds: u64,
}

impl From<NetworkSyncStatus> for SyncProgress {
    fn from(status: NetworkSyncStatus) -> Self {
        SyncProgress {
            state: status.state.into(),
            current_blue_score: status.local_blue_score,
            target_blue_score: status.network_blue_score,
            progress: status.progress as f64,
            headers_downloaded: status.headers_downloaded,
            blocks_downloaded: status.blocks_downloaded,
            blocks_per_second: status.download_rate,
            eta_seconds: status.eta.map(|d| d.as_secs()).unwrap_or(0),
        }
    }
}

/// Sync service manages chain synchronization.
pub struct SyncService {
    /// Storage reference.
    storage: Arc<StorageService>,

    /// Network reference.
    network: Arc<NetworkService>,

    /// Consensus reference.
    consensus: Arc<ConsensusService>,

    /// Current state.
    state: RwLock<SyncState>,

    /// Sync progress.
    progress: RwLock<SyncProgress>,

    /// Is running.
    running: RwLock<bool>,

    /// Shutdown receiver.
    shutdown_rx: RwLock<Option<broadcast::Receiver<()>>>,
}

impl SyncService {
    /// Creates a new sync service.
    pub fn new(
        storage: Arc<StorageService>,
        network: Arc<NetworkService>,
        consensus: Arc<ConsensusService>,
        _config: &NodeConfig,
        shutdown_rx: broadcast::Receiver<()>,
    ) -> anyhow::Result<Self> {
        Ok(SyncService {
            storage,
            network,
            consensus,
            state: RwLock::new(SyncState::Idle),
            progress: RwLock::new(SyncProgress {
                state: SyncState::Idle,
                current_blue_score: 0,
                target_blue_score: 0,
                progress: 0.0,
                headers_downloaded: 0,
                blocks_downloaded: 0,
                blocks_per_second: 0.0,
                eta_seconds: 0,
            }),
            running: RwLock::new(false),
            shutdown_rx: RwLock::new(Some(shutdown_rx)),
        })
    }

    /// Starts the sync service.
    pub async fn start(&self) -> anyhow::Result<()> {
        info!("Starting sync service");

        *self.running.write().await = true;

        // Check current sync status from network layer
        if let Some(status) = self.network.sync_status().await {
            let progress: SyncProgress = status.into();
            *self.state.write().await = progress.state;
            *self.progress.write().await = progress;

            if *self.state.read().await == SyncState::Synced {
                info!("Node is already synced");
                return Ok(());
            }
        }

        // Subscribe to network events and spawn event handler
        let network = self.network.clone();
        let _storage = self.storage.clone();
        let consensus = self.consensus.clone();
        let state = Arc::new(RwLock::new(SyncState::Idle));
        let progress = Arc::new(RwLock::new(SyncProgress {
            state: SyncState::Idle,
            current_blue_score: 0,
            target_blue_score: 0,
            progress: 0.0,
            headers_downloaded: 0,
            blocks_downloaded: 0,
            blocks_per_second: 0.0,
            eta_seconds: 0,
        }));

        // Get network handle and subscribe to events
        if let Some(handle) = network.handle().await {
            let mut event_rx = handle.subscribe();
            let state_clone = state.clone();
            let progress_clone = progress.clone();

            // Spawn event handler task
            tokio::spawn(async move {
                while let Ok(event) = event_rx.recv().await {
                    match event {
                        NetworkEvent::HeadersReceived(headers) => {
                            debug!(count = headers.len(), "Sync received headers");
                            // Headers are processed by the network's SyncManager
                            // We just update our local state
                            if let Ok(status) = handle.sync_status().await {
                                *state_clone.write().await = status.state.into();
                                *progress_clone.write().await = status.into();
                            }
                        }
                        NetworkEvent::BlocksReceived(blocks) => {
                            debug!(count = blocks.len(), "Sync received blocks");
                            // Process blocks through consensus
                            for block in blocks {
                                if let Err(e) = consensus.process_block(&block).await {
                                    warn!("Failed to process synced block: {}", e);
                                }
                            }
                            // Update progress
                            if let Ok(status) = handle.sync_status().await {
                                *state_clone.write().await = status.state.into();
                                *progress_clone.write().await = status.into();
                            }
                        }
                        NetworkEvent::SyncStatusChanged(status) => {
                            info!("Sync status changed: {:?}", status.state);
                            *state_clone.write().await = status.state.into();
                            *progress_clone.write().await = status.into();
                        }
                        _ => {}
                    }
                }
            });

            // Start sync via network handle
            info!("Initiating block synchronization");
            self.network.start_sync().await?;
        } else {
            warn!("Network handle not available, sync will start when network is ready");
        }

        Ok(())
    }

    /// Stops the sync service.
    pub async fn stop(&self) -> anyhow::Result<()> {
        info!("Stopping sync service");
        *self.running.write().await = false;
        Ok(())
    }

    /// Returns current sync state.
    pub async fn state(&self) -> SyncState {
        // Try to get latest from network, fall back to cached
        if let Some(status) = self.network.sync_status().await {
            status.state.into()
        } else {
            *self.state.read().await
        }
    }

    /// Returns sync progress.
    pub async fn progress(&self) -> SyncProgress {
        // Try to get latest from network, fall back to cached
        if let Some(status) = self.network.sync_status().await {
            status.into()
        } else {
            self.progress.read().await.clone()
        }
    }

    /// Checks if synced.
    pub async fn is_synced(&self) -> bool {
        self.state().await == SyncState::Synced
    }

    /// Gets the network's best blue score.
    pub async fn get_network_blue_score(&self) -> u64 {
        if let Some(status) = self.network.sync_status().await {
            status.network_blue_score
        } else {
            0
        }
    }

    /// Downloads headers from peer.
    async fn download_headers(&self, peer_id: &str) -> anyhow::Result<()> {
        debug!(peer = %peer_id, "Downloading headers");

        let locator = self.build_locator().await;
        self.network
            .request_headers(peer_id, locator, [0u8; 32])
            .await?;

        Ok(())
    }

    /// Downloads blocks from peer.
    async fn download_blocks(&self, peer_id: &str, hashes: Vec<[u8; 32]>) -> anyhow::Result<()> {
        debug!(
            peer = %peer_id,
            count = hashes.len(),
            "Downloading blocks"
        );

        self.network.request_blocks(peer_id, hashes).await?;

        Ok(())
    }

    /// Builds a locator for header requests.
    async fn build_locator(&self) -> Vec<[u8; 32]> {
        // Get tips from consensus and build exponential locator
        let tips = self.consensus.tips().await;
        tips.into_iter().take(10).collect()
    }

    /// Processes received headers.
    pub async fn on_headers(&self, headers: Vec<synor_types::BlockHeader>) -> anyhow::Result<()> {
        debug!(count = headers.len(), "Processing received headers");

        // Validate headers through consensus
        for header in &headers {
            if let Err(e) = self.consensus.validate_header(header).await {
                warn!("Invalid header received: {}", e);
                return Err(e);
            }
        }

        // Store validated headers
        for header in headers {
            self.storage.put_header(&header).await?;
        }

        Ok(())
    }

    /// Processes received blocks.
    pub async fn on_blocks(&self, blocks: Vec<synor_types::Block>) -> anyhow::Result<()> {
        debug!(count = blocks.len(), "Processing received blocks");

        for block in blocks {
            // Process through consensus (validates and updates DAG state)
            self.consensus.process_block(&block).await?;
        }

        // Update progress from network status
        if let Some(status) = self.network.sync_status().await {
            *self.state.write().await = status.state.into();
            *self.progress.write().await = status.into();
        }

        Ok(())
    }

    /// Updates progress.
    async fn update_progress(&self, current: u64, target: u64) {
        let mut progress = self.progress.write().await;
        progress.current_blue_score = current;
        progress.target_blue_score = target;
        progress.progress = if target > 0 {
            (current as f64 / target as f64) * 100.0
        } else {
            100.0
        };
    }
}

impl SyncService {
    /// Runs the sync monitoring loop.
    /// This is called as a background task to monitor sync progress.
    #[allow(dead_code)]
    async fn sync_monitor_loop(&self) {
        while *self.running.read().await {
            let state = self.state().await;

            match state {
                SyncState::Idle | SyncState::FindingPeers => {
                    // Wait for peers to connect
                    if self.network.peer_count().await > 0 {
                        // Try to start sync if we have peers
                        if let Err(e) = self.network.start_sync().await {
                            warn!("Failed to start sync: {}", e);
                        }
                    }
                }

                SyncState::Headers | SyncState::Blocks | SyncState::Processing => {
                    // Sync is in progress, just log progress periodically
                    let progress = self.progress().await;
                    info!(
                        state = ?progress.state,
                        headers = progress.headers_downloaded,
                        blocks = progress.blocks_downloaded,
                        progress = format!("{:.2}%", progress.progress),
                        eta = progress.eta_seconds,
                        "Sync progress"
                    );
                }

                SyncState::UtxoSet => {
                    // UTXO snapshot sync (for pruned nodes)
                    // Not implemented yet
                    debug!("UTXO set sync not implemented");
                }

                SyncState::Synced => {
                    // Synced, exit monitor loop
                    info!("Node is fully synced");
                    break;
                }
            }

            tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
        }
    }
}
