use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use dashmap::DashSet;
use log::{debug, warn};
use socks5_proto::Address;

#[derive(Debug)]
pub struct AddressList {
    entries: Arc<DashSet<String>>,
    file_path: PathBuf,
    add_lock: Mutex<()>,
}

pub type ProxyList = AddressList;
pub type DirectList = AddressList;

impl AddressList {
    pub fn new<P: AsRef<Path>>(file_path: P) -> Self {
        let file_path = file_path.as_ref().to_path_buf();
        let entries = Arc::new(DashSet::new());

        let list = Self {
            entries,
            file_path,
            add_lock: Mutex::new(()),
        };
        list.load_from_file();
        list
    }

    fn load_from_file(&self) {
        if !self.file_path.exists() {
            debug!(
                "Address list file {:?} does not exist, starting with empty list",
                self.file_path
            );
            return;
        }

        match File::open(&self.file_path) {
            Ok(file) => {
                let reader = BufReader::new(file);
                for line in reader.lines() {
                    match line {
                        Ok(entry) => {
                            let entry = entry.trim().to_string();
                            if !entry.is_empty() && !entry.starts_with('#') {
                                self.entries.insert(entry);
                            }
                        }
                        Err(e) => {
                            warn!("Error reading line from address list file: {}", e);
                        }
                    }
                }
                debug!(
                    "Loaded {} entries from {:?}",
                    self.entries.len(),
                    self.file_path
                );
            }
            Err(e) => {
                warn!(
                    "Failed to open address list file {:?}: {}",
                    self.file_path, e
                );
            }
        }
    }

    pub fn contains_address(&self, addr: &Address) -> bool {
        match addr {
            Address::DomainAddress(domain, _) => {
                let domain = String::from_utf8_lossy(domain);
                self.contains_domain(&domain)
            }
            Address::SocketAddress(socket_addr) => self.contains_ip(&socket_addr.ip()),
        }
    }

    fn contains_domain(&self, domain: &str) -> bool {
        if self.entries.contains(domain) {
            return true;
        }

        for entry in self.entries.iter() {
            let entry_str = entry.as_str();
            if let Some(base_domain) = entry_str.strip_prefix('.') {
                if domain == base_domain || domain.ends_with(entry_str) {
                    return true;
                }
            }
        }

        let mut current = domain;
        while let Some(idx) = current.find('.') {
            let suffix = &current[idx..];
            if self.entries.contains(suffix) {
                return true;
            }
            current = &current[idx + 1..];
            if current.is_empty() {
                break;
            }
        }

        false
    }

    fn contains_ip(&self, ip: &IpAddr) -> bool {
        let ip_str = ip.to_string();
        if self.entries.contains(&ip_str) {
            return true;
        }

        match ip {
            IpAddr::V4(ipv4) => self.contains_ipv4(ipv4),
            IpAddr::V6(ipv6) => self.contains_ipv6(ipv6),
        }
    }

    fn contains_ipv4(&self, ip: &Ipv4Addr) -> bool {
        let octets = ip.octets();

        for prefix_len in 8..=32 {
            let mask = !0u32 << (32 - prefix_len);
            let network = u32::from_be_bytes(octets) & mask;
            let cidr = format!("{}/{}", Ipv4Addr::from(network.to_be_bytes()), prefix_len);
            if self.entries.contains(&cidr) {
                return true;
            }
        }

        false
    }

    fn contains_ipv6(&self, ip: &Ipv6Addr) -> bool {
        let segments = ip.segments();

        for prefix_len in 8..=128 {
            let full_segments = prefix_len / 16;
            let remaining_bits = prefix_len % 16;

            let mut network_bytes = [0u8; 16];
            for i in 0..full_segments as usize {
                network_bytes[i * 2] = (segments[i] >> 8) as u8;
                network_bytes[i * 2 + 1] = segments[i] as u8;
            }

            if remaining_bits > 0 && full_segments < 8 {
                let mask = !0u16 << (16 - remaining_bits);
                let masked = segments[full_segments as usize] & mask;
                network_bytes[full_segments as usize * 2] = (masked >> 8) as u8;
                network_bytes[full_segments as usize * 2 + 1] = masked as u8;
            }

            let network = Ipv6Addr::from(network_bytes);
            let cidr = format!("{}/{}", network, prefix_len);
            if self.entries.contains(&cidr) {
                return true;
            }
        }

        false
    }

    pub fn add_address(&self, addr: &Address) {
        let _guard = self.add_lock.lock().unwrap();

        let entry = match addr {
            Address::DomainAddress(domain, _) => String::from_utf8_lossy(domain).to_string(),
            Address::SocketAddress(socket_addr) => socket_addr.ip().to_string(),
        };
        if self.entries.contains(&entry) {
            return;
        }

        self.entries.insert(entry.clone());
        self.append_to_file(&entry);
    }

    pub fn remove_address(&self, addr: &Address) {
        let _guard = self.add_lock.lock().unwrap();

        let entry = match addr {
            Address::DomainAddress(domain, _) => String::from_utf8_lossy(domain).to_string(),
            Address::SocketAddress(socket_addr) => socket_addr.ip().to_string(),
        };

        if self.entries.remove(&entry).is_some() {
            self.rewrite_file();
            debug!("Removed entry {} from {:?}", entry, self.file_path);
        }
    }

    fn append_to_file(&self, entry: &str) {
        match File::options()
            .create(true)
            .append(true)
            .open(&self.file_path)
        {
            Ok(mut file) => {
                if let Err(e) = writeln!(file, "{}", entry) {
                    warn!("Failed to write entry {} to file: {}", entry, e);
                } else {
                    debug!("Added entry {} to {:?}", entry, self.file_path);
                }
            }
            Err(e) => {
                warn!("Failed to open address list file for appending: {}", e);
            }
        }
    }

    fn rewrite_file(&self) {
        match File::create(&self.file_path) {
            Ok(mut file) => {
                for entry in self.entries.iter() {
                    if let Err(e) = writeln!(file, "{}", *entry) {
                        warn!("Failed to write entry {} to file: {}", *entry, e);
                    }
                }
            }
            Err(e) => {
                warn!("Failed to rewrite address list file: {}", e);
            }
        }
    }

    pub fn len(&self) -> usize {
        self.entries.len()
    }

    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::net::SocketAddr;
    use tempfile::NamedTempFile;

    #[test]
    fn test_domain_matching_exact() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "example.com\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_domain("example.com"));
        assert!(!list.contains_domain("other.com"));
    }

    #[test]
    fn test_domain_matching_wildcard() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), ".google.com\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_domain("www.google.com"));
        assert!(list.contains_domain("mail.google.com"));
        assert!(list.contains_domain(".google.com"));
        assert!(list.contains_domain("google.com"));
        assert!(list.contains_domain("a.b.google.com"));
        assert!(!list.contains_domain("example.com"));
        assert!(!list.contains_domain("google.com.evil.com"));
    }

    #[test]
    fn test_domain_matching_wildcard_multi_level() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), ".xx.com\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_domain("xx.com"));
        assert!(list.contains_domain("a.xx.com"));
        assert!(list.contains_domain("b.xx.com"));
        assert!(list.contains_domain("c.d.xx.com"));
        assert!(!list.contains_domain("xx.com.evil.com"));
    }

    #[test]
    fn test_ip_matching_exact() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "192.168.1.1\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))));
        assert!(!list.contains_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2))));
    }

    #[test]
    fn test_ip_matching_cidr() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "10.0.0.0/8\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));
        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(10, 255, 255, 255))));
        assert!(!list.contains_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))));
    }

    #[test]
    fn test_ip_matching_cidr_24() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "192.168.1.0/24\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))));
        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255))));
        assert!(!list.contains_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 2, 1))));
    }

    #[test]
    fn test_address_matching_domain() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "example.com\n").unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_address(&Address::DomainAddress(b"example.com".to_vec(), 443)));
        assert!(!list.contains_address(&Address::DomainAddress(b"other.com".to_vec(), 443)));
    }

    #[test]
    fn test_address_matching_ip() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "192.168.1.0/24\n").unwrap();

        let list = AddressList::new(temp_file.path());

        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 443);
        assert!(list.contains_address(&Address::SocketAddress(addr)));

        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 2, 100)), 443);
        assert!(!list.contains_address(&Address::SocketAddress(addr)));
    }

    #[test]
    fn test_add_domain() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "existing.com\n").unwrap();

        let list = AddressList::new(temp_file.path());
        assert!(list.contains_domain("existing.com"));
        assert_eq!(list.len(), 1);

        list.add_address(&Address::DomainAddress(b"new.com".to_vec(), 443));

        assert!(list.contains_domain("new.com"));
        assert_eq!(list.len(), 2);

        let contents = fs::read_to_string(temp_file.path()).unwrap();
        assert!(contents.contains("new.com"));
    }

    #[test]
    fn test_add_ip() {
        let temp_file = NamedTempFile::new().unwrap();

        let list = AddressList::new(temp_file.path());
        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 443);
        list.add_address(&Address::SocketAddress(addr));

        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));

        let contents = fs::read_to_string(temp_file.path()).unwrap();
        assert!(contents.contains("10.0.0.1"));
    }

    #[test]
    fn test_remove_domain() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "example.com\nother.com\n").unwrap();

        let list = AddressList::new(temp_file.path());
        assert!(list.contains_domain("example.com"));
        assert_eq!(list.len(), 2);

        list.remove_address(&Address::DomainAddress(b"example.com".to_vec(), 443));

        assert!(!list.contains_domain("example.com"));
        assert!(list.contains_domain("other.com"));
        assert_eq!(list.len(), 1);

        let contents = fs::read_to_string(temp_file.path()).unwrap();
        assert!(!contents.contains("example.com"));
        assert!(contents.contains("other.com"));
    }

    #[test]
    fn test_remove_ip() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "10.0.0.1\n10.0.0.2\n").unwrap();

        let list = AddressList::new(temp_file.path());
        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 443);
        list.remove_address(&Address::SocketAddress(addr));

        assert!(!list.contains_ip(&IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));
        assert!(list.contains_ip(&IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2))));

        let contents = fs::read_to_string(temp_file.path()).unwrap();
        assert!(!contents.contains("10.0.0.1"));
        assert!(contents.contains("10.0.0.2"));
    }

    #[test]
    fn test_remove_nonexistent() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(temp_file.path(), "existing.com\n").unwrap();

        let list = AddressList::new(temp_file.path());
        list.remove_address(&Address::DomainAddress(b"nonexistent.com".to_vec(), 443));

        assert_eq!(list.len(), 1);
        assert!(list.contains_domain("existing.com"));
    }

    #[test]
    fn test_empty_file() {
        let temp_file = NamedTempFile::new().unwrap();
        let list = AddressList::new(temp_file.path());

        assert!(list.is_empty());
        assert!(!list.contains_domain("any.com"));
    }

    #[test]
    fn test_nonexistent_file() {
        let list = AddressList::new("/nonexistent/path/list.txt");
        assert!(list.is_empty());
    }

    #[test]
    fn test_comments_and_whitespace() {
        let temp_file = NamedTempFile::new().unwrap();
        fs::write(
            temp_file.path(),
            "# comment\n  trimmed.com  \n\n# another comment\n",
        )
        .unwrap();

        let list = AddressList::new(temp_file.path());

        assert!(list.contains_domain("trimmed.com"));
        assert_eq!(list.len(), 1);
    }

    #[test]
    fn test_type_aliases() {
        let temp_file = NamedTempFile::new().unwrap();

        let proxy_list: ProxyList = ProxyList::new(temp_file.path());
        assert!(proxy_list.is_empty());

        let direct_list: DirectList = DirectList::new(temp_file.path());
        assert!(direct_list.is_empty());
    }
}
