use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use smb::{
    resource::GetLen, Client, ClientConfig, CreateOptions, FileAccessMask, FileAttributes,
    FileCreateArgs, ReadAt, UncPath, WriteAt,
};
use std::str::FromStr;
use url::Url;

/// smb://server/share/path → \\server\share[\path...]
fn smb_url_to_unc(url_str: &str) -> Result<UncPath> {
    let url = Url::parse(url_str).context("Invalid SMB URL")?;
    let host = url
        .host_str()
        .ok_or_else(|| anyhow!("No host in SMB URL"))?;
    let path_segments: Vec<&str> = url.path().trim_matches('/').split('/').collect();
    let share = path_segments.first().cloned().unwrap_or("IPC$");
    let base = UncPath::from_str(&format!("\\\\{}\\{}", host, share))
        .map_err(|e| anyhow!("Invalid UNC: {}", e))?;
    let mut full = base.clone();
    for &segment in path_segments.iter().skip(1) {
        if !segment.is_empty() {
            full = full.with_path(segment);
        }
    }
    Ok(full)
}

// ---------- Универсальный трейт ----------

#[async_trait]
pub trait FileAccess: Send + Sync {
    async fn list_files(&self) -> Result<Vec<String>>;
    async fn read_file(&self, filename: &str) -> Result<Vec<u8>>;
    async fn write_file(&self, filename: &str, data: &[u8]) -> Result<()>;
    async fn delete_file(&self, filename: &str) -> Result<()>;
}

// ---------- Локальная файловая система ----------

pub struct LocalFileAccess {
    base_path: String,
}

impl LocalFileAccess {
    pub fn new(base_path: &str) -> Self {
        Self {
            base_path: base_path.to_string(),
        }
    }

    fn full_path(&self, filename: &str) -> String {
        format!("{}/{}", self.base_path, filename)
    }
}

#[async_trait]
impl FileAccess for LocalFileAccess {
    async fn list_files(&self) -> Result<Vec<String>> {
        let mut files = Vec::new();
        let mut entries = tokio::fs::read_dir(&self.base_path).await?;
        while let Some(entry) = entries.next_entry().await? {
            if entry.file_type().await?.is_file() {
                if let Some(name) = entry.file_name().to_str() {
                    files.push(name.to_string());
                }
            }
        }
        Ok(files)
    }

    async fn read_file(&self, filename: &str) -> Result<Vec<u8>> {
        let path = self.full_path(filename);
        tokio::fs::read(&path).await.map_err(Into::into)
    }

    async fn write_file(&self, filename: &str, data: &[u8]) -> Result<()> {
        let path = self.full_path(filename);
        tokio::fs::write(&path, data).await.map_err(Into::into)
    }

    async fn delete_file(&self, filename: &str) -> Result<()> {
        let path = self.full_path(filename);
        tokio::fs::remove_file(&path).await.map_err(Into::into)
    }
}

// ---------- SMB ----------

#[derive(Clone)]
pub struct SmbAccessConfig {
    pub url: String,
    pub user: Option<String>,
    pub password: Option<String>,
}

pub struct SmbFileAccess {
    config: SmbAccessConfig,
}

impl SmbFileAccess {
    pub fn new(config: SmbAccessConfig) -> Self {
        Self { config }
    }

    async fn connect(&self) -> Result<(Client, UncPath)> {
        let base_unc = smb_url_to_unc(&self.config.url)?;
        let client = Client::new(ClientConfig::default());

        if let (Some(user), Some(pass)) = (&self.config.user, &self.config.password) {
            client.share_connect(&base_unc, user, pass.clone()).await?;
        } else {
            client.share_connect(&base_unc, "", "".to_string()).await?;
        }
        Ok((client, base_unc))
    }

    fn full_unc_path(&self, base: &UncPath, filename: &str) -> UncPath {
        let segments: Vec<&str> = filename.split('/').filter(|s| !s.is_empty()).collect();
        let mut path = base.clone();
        for seg in segments {
            path = path.with_path(seg);
        }
        path
    }
}

#[async_trait]
impl FileAccess for SmbFileAccess {
    async fn list_files(&self) -> Result<Vec<String>> {
        // SMB list directory пока не реализован в текущем API,
        // оставим заглушку для будущей доработки
        Ok(Vec::new())
    }

    async fn read_file(&self, filename: &str) -> Result<Vec<u8>> {
        let (client, base_unc) = self.connect().await?;
        let file_path = self.full_unc_path(&base_unc, filename);

        let args =
            FileCreateArgs::make_open_existing(FileAccessMask::new().with_generic_read(true));
        let resource = client.create_file(&file_path, &args).await?;
        let file = resource.unwrap_file();

        let file_size = file.get_len().await? as usize;
        let mut buf = vec![0u8; file_size];
        let bytes_read = file.read_at(&mut buf, 0).await?;
        buf.truncate(bytes_read);

        file.close().await?;
        Ok(buf)
    }

    async fn write_file(&self, filename: &str, data: &[u8]) -> Result<()> {
        let (client, base_unc) = self.connect().await?;
        let file_path = self.full_unc_path(&base_unc, filename);

        let args =
            FileCreateArgs::make_overwrite(FileAttributes::default(), CreateOptions::default());
        let resource = client.create_file(&file_path, &args).await?;
        let remote_file = resource.unwrap_file();

        remote_file.write_at(data, 0).await?;
        remote_file.close().await?;
        Ok(())
    }

    async fn delete_file(&self, _filename: &str) -> Result<()> {
        // Удаление будет добавлено позже
        Err(anyhow!("Delete not yet implemented for SMB"))
    }
}

// ---------- Synology ----------

pub struct SynologyFileAccess {
    client: crate::synology::SynologyClient,
    base_path: String,
}

impl SynologyFileAccess {
    pub fn new(client: crate::synology::SynologyClient, base_path: &str) -> Self {
        Self {
            client,
            base_path: base_path.to_string(),
        }
    }

    fn full_path(&self, filename: &str) -> String {
        format!("{}/{}", self.base_path, filename)
    }
}

#[async_trait]
impl FileAccess for SynologyFileAccess {
    async fn list_files(&self) -> Result<Vec<String>> {
        use crate::synology::ListFilesOptions;
        let result = self
            .client
            .list_files(&self.base_path, ListFilesOptions::new())
            .await
            .map_err(|e| anyhow::anyhow!(e))?;
        let files = result
            .files
            .into_iter()
            .filter(|f| !f.isdir)
            .map(|f| f.name)
            .collect();
        Ok(files)
    }

    async fn read_file(&self, filename: &str) -> Result<Vec<u8>> {
        let path = self.full_path(filename);
        self.client
            .download(&path)
            .await
            .map_err(|e| anyhow::anyhow!(e))
    }

    async fn write_file(&self, filename: &str, data: &[u8]) -> Result<()> {
        self.client
            .upload(
                &self.base_path,
                filename,
                data.to_vec(),
                Some(false),
                Some(true),
            )
            .await
            .map_err(|e| anyhow::anyhow!(e))
    }

    async fn delete_file(&self, filename: &str) -> Result<()> {
        let path = self.full_path(filename);
        self.client
            .delete(&[&path], None)
            .await
            .map_err(|e| anyhow::anyhow!(e))
    }
}
