use anyhow::{anyhow, Result};
use std::sync::Arc;
use crate::config::{ClientConfig, InboundConfig};
#[allow(unused_imports)]
use crate::tunnel::router::{Router, Session};
use crate::tunnel::outbounds::OutboundManager;
use tokio::sync::watch;

#[cfg(any(target_os = "windows", target_os = "linux", target_os = "android"))]
pub async fn run_tun_inbound(
    config: ClientConfig,
    inbound_config: InboundConfig,
    router: Arc<Router>,
    outbound_manager: Arc<OutboundManager>,
    mut shutdown: watch::Receiver<bool>,
    metrics: Arc<crate::bridge::BridgeMetrics>,
) -> Result<()> {

    use netstack_smoltcp::StackBuilder;
    use portable_atomic::Ordering;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    use futures::{StreamExt, SinkExt};

    let InboundConfig::Tun { tag, auto_route, mtu, .. } = inbound_config else {
        return Err(anyhow!("Invalid config for TUN inbound"));
    };

    tracing::info!("Starting TUN inbound (tag: {}, auto_route: {}, mtu: {})", tag, auto_route, mtu);

    #[cfg(target_os = "windows")]
    let _phys_if_for_bypass: Option<u32> = ostp_tun::windows::windows_route::sys::get_default_ipv4_route().map(|(_, idx)| idx);
    #[cfg(not(target_os = "windows"))]
    let _phys_if_for_bypass: Option<u32> = None;

    let mut bypass_ips: Vec<std::net::IpAddr> = Vec::new();
    
    // Bypass all outbound server IPs
    for outbound in &config.outbounds {
        let server = match outbound {
            crate::config::OutboundConfig::Ostp { server, .. } => Some(server),
            crate::config::OutboundConfig::Socks { server, .. } => Some(server),
            _ => None,
        };
        if let Some(host) = server {
            if let Ok(ip) = host.parse::<std::net::IpAddr>() {
                bypass_ips.push(ip);
            } else {
                if let Ok(addrs) = tokio::net::lookup_host((host.as_str(), 443)).await {
                    for addr in addrs {
                        bypass_ips.push(addr.ip());
                    }
                }
            }
        }
    }

    // Build smoltcp network stack with proper buffer sizes for throughput
    let (stack, tcp_runner, udp_socket, tcp_listener) = StackBuilder::default()
        .stack_buffer_size(65536)   // 64KB for packet accumulation
        .tcp_buffer_size(131072)    // 128KB for TCP streams
        .udp_buffer_size(65536)     // 64KB for UDP datagrams
        .enable_tcp(true)
        .enable_udp(true)
        .mtu(mtu)
        .build()?;

    let mut runner_task = tokio::spawn(async move {
        if let Some(runner) = tcp_runner {
            let _ = runner.await;
        }
    });

    let (mut stack_sink, mut stack_stream) = stack.split();

    #[allow(unused_variables)]
    let mut _route_guard = None;

    let (tun_to_stack, stack_to_tun) = {
        #[cfg(target_os = "android")]
        {
            if let Some(fd) = fd {
                use std::os::fd::{FromRawFd, AsRawFd};
                use tokio::io::unix::AsyncFd;
                use std::os::unix::io::OwnedFd;
                
                let async_fd = AsyncFd::new(unsafe { OwnedFd::from_raw_fd(fd) })?;
                let async_fd_shared = std::sync::Arc::new(async_fd);
                
                let afd1 = async_fd_shared.clone();
                let tun_to_stack = tokio::spawn(async move {
                    let mut frame = vec![0u8; 65535];
                    loop {
                        let mut guard = match afd1.readable().await {
                            Ok(g) => g,
                            Err(_) => break,
                        };
                        match guard.try_io(|inner| {
                            let res = unsafe { libc::read(inner.as_raw_fd(), frame.as_mut_ptr() as *mut libc::c_void, frame.len()) };
                            if res < 0 {
                                let err = std::io::Error::last_os_error();
                                if err.kind() == std::io::ErrorKind::WouldBlock { Err(err) } else { Ok(res as isize) }
                            } else { Ok(res as isize) }
                        }) {
                            Ok(Ok(n)) if n > 0 => {
                                if let Err(_) = stack_sink.send(frame[..n as usize].to_vec()).await { break; }
                            }
                            Ok(Ok(_)) => break,
                            Ok(Err(_)) => break,
                            Err(_) => continue,
                        }
                    }
                });

                let afd2 = async_fd_shared.clone();
                let stack_to_tun = tokio::spawn(async move {
                    while let Some(Ok(frame)) = stack_stream.next().await {
                        let mut written = 0;
                        while written < frame.len() {
                            let mut guard = match afd2.writable().await {
                                Ok(g) => g,
                                Err(_) => break,
                            };
                            match guard.try_io(|inner| {
                                let res = unsafe { libc::write(inner.as_raw_fd(), frame[written..].as_ptr() as *const libc::c_void, frame.len() - written) };
                                if res < 0 {
                                    let err = std::io::Error::last_os_error();
                                    if err.kind() == std::io::ErrorKind::WouldBlock { Err(err) } else { Ok(res as isize) }
                                } else { Ok(res as isize) }
                            }) {
                                Ok(Ok(n)) if n > 0 => written += n as usize,
                                Ok(Ok(_)) => break,
                                Ok(Err(_)) => break,
                                Err(_) => continue,
                            }
                        }
                    }
                });
                (tun_to_stack, stack_to_tun)
            } else {
                return Err(anyhow!("FD is required on Android but not provided"));
            }
        }

        #[cfg(not(target_os = "android"))]
        {
            let opts = ostp_tun::OstpTunOptions {
                server_ip: bypass_ips.first().copied().unwrap_or_else(|| "127.0.0.1".parse().unwrap()),
                bypass_ips: bypass_ips,
                dns_server: None,
                kill_switch: false,
                mtu: mtu as u16,
                wintun_path: None,
            };

            let tun_interface = ostp_tun::OstpTunInterface::create(opts)
                .await
                .map_err(|e| anyhow!("Failed to create OstpTunInterface: {}", e))?;
                
            let dev = tun_interface.device;
            _route_guard = Some(tun_interface.guard);

            let (mut tun_read, mut tun_write) = tokio::io::split(dev);
            let m_sent = metrics.clone();
            let tun_to_stack = tokio::spawn(async move {
                let mut buf = vec![0u8; 65536];
                loop {
                    match tun_read.read(&mut buf).await {
                        Ok(0) => break,
                        Ok(n) => {
                            m_sent.bytes_sent.fetch_add(n as u64, Ordering::Relaxed);
                            if let Err(_) = stack_sink.send(buf[..n].to_vec()).await { break; }
                        }
                        Err(e) => tracing::debug!("tun_read error: {e}"),
                    }
                }
            });
            let m_recv = metrics.clone();
            let stack_to_tun = tokio::spawn(async move {
                while let Some(Ok(frame)) = stack_stream.next().await {
                    m_recv.bytes_recv.fetch_add(frame.len() as u64, Ordering::Relaxed);
                    if let Err(e) = tun_write.write(&frame).await { tracing::debug!("tun_write error: {e}"); }
                }
            });
            (tun_to_stack, stack_to_tun)
        }
    };

    // TUN device is up and the default route has been installed inside
    // OstpTunInterface::create — the tunnel is now carrying traffic.
    metrics.connection_state.store(2, Ordering::Relaxed);
    tracing::info!("TUN inbound ready, connection state = connected");

    // ── TCP Handler ──
    let outbound_manager_tcp = outbound_manager.clone();
    let router_tcp = router.clone();
    let tag_tcp = tag.clone();
    
    let tcp_accept_task = tokio::spawn(async move {
        let Some(mut listener) = tcp_listener else { return; };
        while let Some((mut stream, local, remote)) = listener.next().await {
            let om = outbound_manager_tcp.clone();
            let rt = router_tcp.clone();
            let ib_tag = tag_tcp.clone();
            
            tokio::spawn(async move {
                let process_name = crate::tunnel::process_lookup::get_process_name_from_port(local.port());

                let mut sniff_buf = [0u8; 2048];
                let sniff_len = match tokio::time::timeout(
                    std::time::Duration::from_millis(100),
                    stream.read(&mut sniff_buf)
                ).await {
                    Ok(Ok(n)) => n,
                    _ => 0,
                };
                
                let mut domain_suffix = None;
                if sniff_len > 0 {
                    domain_suffix = crate::tunnel::sni_sniff::extract_sni(&sniff_buf[..sniff_len]);
                }

                let session = Session {
                    protocol: "tcp".to_string(),
                    inbound_tag: ib_tag.clone(),
                    source_ip: Some(local.ip()),
                    destination_ip: Some(remote.ip()),
                    destination_port: remote.port(),
                    sni: domain_suffix.map(|s| s.to_string()),
                    process_name,
                };

                let outbound_tag = rt.route(&session);
                tracing::info!("TUN TCP {} -> {} routed to {}", local, remote, outbound_tag);

                let target_host = if let Some(domain) = session.sni {
                    domain
                } else {
                    remote.ip().to_string()
                };

                match om.dial_tcp(&outbound_tag, &target_host, session.destination_port).await {
                    Ok(mut remote_stream) => {
                        if sniff_len > 0 {
                            if let Err(e) = remote_stream.write_all(&sniff_buf[..sniff_len]).await {
                                tracing::warn!("Failed to forward sniffed bytes to {}: {}", outbound_tag, e);
                                return;
                            }
                        }
                        let _ = tokio::io::copy_bidirectional(&mut stream, &mut remote_stream).await;
                    }
                    Err(e) => {
                        tracing::warn!("TUN TCP dial failed to {}: {}", outbound_tag, e);
                    }
                }
            });
        }
    });

    // ── UDP Handler ──
    let outbound_manager_udp = outbound_manager.clone();
    let router_udp = router.clone();
    let tag_udp = tag.clone();
    
    let udp_proxy_task = tokio::spawn(async move {
        if let Some(udp_sock) = udp_socket {
            let (mut udp_rx, _udp_tx) = udp_sock.split();
            while let Some((payload, local, remote)) = udp_rx.next().await {
                let process_name = crate::tunnel::process_lookup::get_process_name_from_port_udp(local.port());
                let session = Session {
                    protocol: "udp".to_string(),
                    inbound_tag: tag_udp.clone(),
                    source_ip: Some(local.ip()),
                    destination_ip: Some(remote.ip()),
                    destination_port: remote.port(),
                    sni: None,
                    process_name,
                };
                let outbound_tag = router_udp.route(&session);
                
                let payload_bytes = bytes::Bytes::copy_from_slice(&payload);
                if let Err(e) = outbound_manager_udp.handle_udp(&outbound_tag, local, remote, payload_bytes).await {
                    tracing::debug!("TUN UDP drop to {}: {}", outbound_tag, e);
                }
            }
        }
    });

    tokio::select! {
        _ = shutdown.changed() => {
            tracing::info!("TUN inbound {} shutting down", tag);
        }
        _ = &mut runner_task => {}
    }
    
    tun_to_stack.abort();
    stack_to_tun.abort();
    tcp_accept_task.abort();
    udp_proxy_task.abort();

    Ok(())
}

#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "android")))]
pub async fn run_tun_inbound(
    _config: ClientConfig,
    _inbound_config: InboundConfig,
    _router: Arc<Router>,
    _outbound_manager: Arc<OutboundManager>,
    _shutdown: watch::Receiver<bool>,
    _metrics: Arc<crate::bridge::BridgeMetrics>,
) -> Result<()> {
    Err(anyhow!("TUN is only supported on Windows and Linux"))
}
