use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant};

use anyhow::{Context, Result};
use tokio::sync::Mutex;
use tokio::io::AsyncReadExt;
use tracing::{debug, error, info, warn};

use serde::{Deserialize, Serialize};

use iroh::{
    base::{
        node_addr::AddrInfoOptions,
        ticket::Ticket,
    },
    client::{
        docs::{Doc, Entry, LiveEvent, ShareMode},
        Iroh,
    },
    docs::{
        AuthorId, ContentStatus, DocTicket, NamespaceId,
        store::Query,
    },
    net::{NodeId, NodeAddr},
    node::Node,
    blobs::Hash,
};
use futures_lite::StreamExt;


/// File metadata stored in docs
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FileMetadata {
    pub size: u64,
    pub modified: u64,
}

#[derive(Serialize)]
pub struct SyncStatus {
    pub node_id: String,
    pub namespace_id: String,
    pub connected_peers: usize,
    pub total_files: usize,
    pub total_directories: usize,
}

/// Main sync directory handler
pub struct SyncDir {
    base_path: PathBuf,
    node: Node<iroh::blobs::store::fs::Store>,
    client: Iroh,
    author: AuthorId,
    doc: Arc<Mutex<Doc>>,
    pending_entries: Arc<Mutex<HashMap<String, Entry>>>,
    recently_written: Arc<Mutex<HashMap<PathBuf, Instant>>>,
    connected_peers: Arc<Mutex<HashSet<NodeId>>>,
    failed_files: Arc<Mutex<Vec<(String, String)>>>,
}

impl SyncDir {
    /// Create a new SyncDir instance
    pub async fn new(base_path: PathBuf, ticket: Option<String>) -> Result<Self> {
        info!("Initializing SyncDir at {:?}", base_path);

        // --- THIS IS THE CRITICAL FIX ---
        // Instead of using the current working directory, we create the iroh data
        // directory INSIDE the safe, writable path provided by Flutter.
        let data_path = base_path.join(".iroh-node-data");
        // ---------------------------------

        fs::create_dir_all(&data_path)?;
        info!("Using data directory: {:?}", data_path);

        // Create the node using the corrected, writable path
        let node = Node::persistent(&data_path)
            .await?
            .enable_docs()
            .spawn()
            .await?;

        let cloned_node = node.clone();
        let client = cloned_node.client();
        
        let author = client.authors().default().await?;
        info!("Using author: {}", author);

        let doc = match ticket {
            Some(ticket_str) => {
                info!("Importing from ticket: {}", ticket_str);
                let ticket: DocTicket = ticket_str.parse()
                    .context("Failed to parse document ticket")?;
                
                client.docs().import(ticket).await?
            }
            None => {
                info!("Creating new document");
                let doc = client.docs().create().await?;
                
                let ticket = doc.share(ShareMode::Write, AddrInfoOptions::RelayAndAddresses).await?;
                println!("=== DOCUMENT TICKET ===");
                println!("{}", ticket);
                println!("=======================");
                info!("Created new doc with ticket: {}", ticket);
                
                doc
            }
        };

        let doc = Arc::new(Mutex::new(doc));

        Ok(SyncDir {
            base_path,
            node,
            client: client.clone(),
            author,
            doc,
            pending_entries: Arc::new(Mutex::new(HashMap::new())),
            recently_written: Arc::new(Mutex::new(HashMap::new())),
            connected_peers: Arc::new(Mutex::new(HashSet::new())),
            failed_files: Arc::new(Mutex::new(Vec::new())),
        })
    }

    /// Load all local files into the sync system
    pub async fn load_local_files(&self) -> Result<()> {
        info!("Loading local files from {:?}", self.base_path);
        self.scan_and_add_directory(&self.base_path).await?;
        info!("Local file loading complete");
        Ok(())
    }

    async fn scan_and_add_directory(&self, dir_path: &Path) -> Result<()> {
        // We need to ignore our own data directory to prevent an infinite loop
        let node_data_path = self.base_path.join(".iroh-node-data");
        if dir_path == node_data_path {
            info!("Skipping node data directory scan.");
            return Ok(());
        }

        info!("Scanning directory: {:?}", dir_path);
        let entries = fs::read_dir(dir_path)?;

        for entry in entries {
            let entry = entry?;
            let path = entry.path();
            let metadata = entry.metadata()?;

            if metadata.is_dir() {
                self.add_directory_to_sync(&path).await?;
                Box::pin(self.scan_and_add_directory(&path)).await?;
            } else if metadata.is_file() {
                self.add_file_to_sync(&path).await?;
            }
        }
        Ok(())
    }

    async fn add_directory_to_sync(&self, dir_path: &Path) -> Result<()> {
        let relative_path = self.get_relative_path(dir_path)?;
        let key = format!("d{}", relative_path.to_string_lossy());

        if self.entry_exists(&key).await? {
            debug!("Directory entry already exists: {}", relative_path.display());
            return Ok(());
        }

        let doc = self.doc.lock().await;
        doc.set_bytes(self.author, key.clone(), vec![0u8]).await?;
        drop(doc);
        info!("Added directory: {}", relative_path.display());
        Ok(())
    }

    async fn add_file_to_sync(&self, file_path: &Path) -> Result<()> {
        let relative_path = self.get_relative_path(file_path)?;
        let key = relative_path.to_string_lossy().to_string();

        if key.ends_with(":Zone.Identifier") {
            debug!("Skipping Zone.Identifier file: {}", key);
            return Ok(());
        }

        let content = fs::read(file_path)?;
        let content_to_store = if content.is_empty() { vec![0u8] } else { content };

        if self.entry_exists_with_content(&key, &content_to_store).await? {
            debug!("File entry already exists with same content: {}", key);
            return Ok(());
        }

        let doc = self.doc.lock().await;
        doc.set_bytes(self.author, key.clone(), content_to_store).await?;
        drop(doc);
        info!("Added file: {}", key);
        Ok(())
    }

    async fn download_blob(&self, hash: Hash) -> Result<Vec<u8>> {
        let peers = self.connected_peers.lock().await.clone();
        if peers.is_empty() {
            return Err(anyhow::anyhow!("No connected peers to download from"));
        }
        
        for peer_id in peers {
            let node_addr = NodeAddr::new(peer_id);
            if let Ok(mut progress) = self.client.blobs().download(hash, node_addr).await {
                while progress.next().await.is_some() {}
                if let Ok(mut reader) = self.client.blobs().read(hash).await {
                    let mut content = Vec::new();
                    reader.read_to_end(&mut content).await?;
                    return Ok(content);
                }
            }
        }
        Err(anyhow::anyhow!("Failed to download blob from any connected peer"))
    }

    pub async fn write_remote_entry(&self, entry: Entry) -> Result<()> {
        let key = String::from_utf8_lossy(entry.key()).to_string();
        
        if key.starts_with("d") {
            let dir_path = &key[1..];
            let full_dir_path = self.base_path.join(dir_path);
            fs::create_dir_all(&full_dir_path)?;
            
            let mut recently_written = self.recently_written.lock().await;
            recently_written.insert(full_dir_path.clone(), Instant::now());
            info!("Created directory: {}", full_dir_path.display());
            return Ok(());
        }
        
        let content: Vec<u8> = {
            let doc = self.doc.lock().await;
            match entry.content_bytes(&*doc).await {
                Ok(bytes) => bytes.to_vec(),
                Err(_) => self.download_blob(entry.content_hash()).await?,
            }
        };
        
        let actual_content = if content == vec![0u8] { Vec::new() } else { content };
        let file_path = self.base_path.join(&key);
        
        if let Some(parent) = file_path.parent() {
            fs::create_dir_all(parent)?;
        }
        
        fs::write(&file_path, &actual_content)?;
        
        let mut recently_written = self.recently_written.lock().await;
        recently_written.insert(file_path.clone(), Instant::now());
        info!("Wrote file: {} ({} bytes)", file_path.display(), actual_content.len());
        Ok(())
    }

    pub async fn handle_pending_entry(&self, entry: Entry) -> Result<()> {
        let key = String::from_utf8_lossy(entry.key()).to_string();
        let mut pending = self.pending_entries.lock().await;
        pending.insert(key.clone(), entry);
        info!("Stored pending entry: {}", key);
        Ok(())
    }

    pub async fn process_pending_entries(&self) -> Result<()> {
        let mut pending = self.pending_entries.lock().await;
        let entries_to_process: Vec<_> = pending.drain().collect();
        drop(pending);
        
        for (key, entry) in entries_to_process {
            if let Err(e) = self.write_remote_entry(entry).await {
                error!("Failed to process pending entry {}: {}", key, e);
            }
        }
        Ok(())
    }

    pub async fn add_peer(&self, peer_id: NodeId) {
        self.connected_peers.lock().await.insert(peer_id);
    }

    pub async fn remove_peer(&self, peer_id: NodeId) {
        self.connected_peers.lock().await.remove(&peer_id);
    }

    pub async fn handle_local_change(&self, path: PathBuf) -> Result<()> {
        if self.was_recently_written(&path).await {
            return Ok(());
        }
        
        if path.is_file() {
            self.add_file_to_sync(&path).await?;
        } else if path.is_dir() {
            self.add_directory_to_sync(&path).await?;
        }
        Ok(())
    }

    async fn was_recently_written(&self, path: &Path) -> bool {
        let mut recently_written = self.recently_written.lock().await;
        recently_written.retain(|_, time| time.elapsed() < Duration::from_secs(2));
        recently_written.contains_key(path)
    }

    pub async fn handle_local_delete(&self, path: PathBuf) -> Result<()> {
        let relative_path = self.get_relative_path(&path)?;
        let file_key = relative_path.to_string_lossy().to_string();
        let dir_key = format!("d{}", file_key);

        let doc = self.doc.lock().await;
        let _ = doc.del(self.author, file_key).await;
        let _ = doc.del(self.author, dir_key).await;
        Ok(())
    }

    async fn entry_exists(&self, key: &str) -> Result<bool> {
        let doc = self.doc.lock().await;
        let mut entries = doc.get_many(Query::single_latest_per_key().key_exact(key.to_string())).await?;
        Ok(entries.next().await.is_some())
    }

    async fn entry_exists_with_content(&self, key: &str, content: &[u8]) -> Result<bool> {
        let doc = self.doc.lock().await;
        if let Some(Ok(entry)) = doc.get_many(Query::single_latest_per_key().key_exact(key.to_string())).await?.next().await {
            if let Ok(existing_content) = entry.content_bytes(&*doc).await {
                return Ok(existing_content.as_ref() == content);
            }
        }
        Ok(false)
    }

    fn get_relative_path(&self, path: &Path) -> Result<PathBuf> {
        path.strip_prefix(&self.base_path).map(|p| p.to_path_buf()).context("Failed to get relative path")
    }

    pub async fn subscribe(&self) -> Result<impl futures_lite::Stream<Item = Result<LiveEvent>>> {
        let doc = self.doc.lock().await;
        doc.subscribe().await.map_err(Into::into)
    }

    pub fn node_id(&self) -> NodeId {
        self.node.node_id()
    }

    pub async fn namespace_id(&self) -> Result<NamespaceId> {
        Ok(self.doc.lock().await.id())
    }

    pub async fn shutdown(&self) -> Result<()> {
        self.node.clone().shutdown().await?;
        Ok(())
    }
    
    pub async fn get_sync_status(&self) -> Result<SyncStatus> {
        let doc = self.doc.lock().await;
        let mut entries = doc.get_many(Query::single_latest_per_key()).await?;
        let mut file_count = 0;
        let mut dir_count = 0;

        while let Some(entry) = entries.next().await {
            if let Ok(e) = entry {
                if String::from_utf8_lossy(e.key()).starts_with("d") {
                    dir_count += 1;
                } else {
                    file_count += 1;
                }
            }
        }
        drop(doc);

        let connected_peers = self.connected_peers.lock().await.len();

        Ok(SyncStatus {
            node_id: self.node_id().to_string(),
            namespace_id: self.namespace_id().await?.to_string(),
            connected_peers,
            total_files: file_count,
            total_directories: dir_count,
        })
    }

    pub async fn generate_ticket(&self) -> Result<String> {
        let doc = self.doc.lock().await;
        let ticket = doc.share(ShareMode::Write, AddrInfoOptions::RelayAndAddresses).await?;
        Ok(ticket.to_string())
    }
}
