use std::sync::Arc;

use rustler::{Encoder, ResourceArc};

use crate::{IpOrSelf, Result, TOKIO_RUNTIME, atoms, erl_result, ip_from_erl, ok_arc};

pub(crate) struct TcpListener {
    inner: Arc<tailscale::netstack::TcpListener>,
}

pub(crate) struct TcpStream {
    inner: Arc<tailscale::netstack::TcpStream>,
}

#[rustler::resource_impl]
impl rustler::Resource for TcpListener {}

#[rustler::resource_impl]
impl rustler::Resource for TcpStream {}

#[rustler::nif(schedule = "DirtyIo")]
fn tcp_listen(
    env: rustler::Env,
    dev: ResourceArc<crate::Device>,
    addr: rustler::Term,
    port: u16,
) -> impl Encoder {
    let dev = dev.inner.clone();
    let ip = IpOrSelf::new(addr);

    let sock = TOKIO_RUNTIME.block_on(async move {
        let addr = ip.ok_or("invalid ip addr")?.resolve(&dev).await?;
        let sock = dev.tcp_listen((addr, port).into()).await?;

        ok_arc(TcpListener {
            inner: Arc::new(sock),
        })
    });

    erl_result(env, sock)
}

#[rustler::nif]
fn tcp_listen_local_addr(env: rustler::Env, listener: ResourceArc<TcpListener>) -> impl Encoder {
    crate::sockaddr_to_erl(env, listener.inner.local_addr())
}

#[rustler::nif(schedule = "DirtyIo")]
fn tcp_connect(
    env: rustler::Env<'_>,
    dev: ResourceArc<crate::Device>,
    addr: rustler::Term,
    port: u16,
) -> impl Encoder {
    let addr = ip_from_erl(addr);
    let dev = dev.inner.clone();

    let sock = TOKIO_RUNTIME.block_on(async move {
        let addr = addr.ok_or("invalid ip addr")?;
        let sock = dev.tcp_connect((addr, port).into()).await?;

        ok_arc(TcpStream {
            inner: Arc::new(sock),
        })
    });

    erl_result(env, sock)
}

#[rustler::nif(schedule = "DirtyIo")]
fn tcp_connect_by_name(
    env: rustler::Env<'_>,
    dev: ResourceArc<crate::Device>,
    name: &str,
    port: u16,
) -> impl Encoder {
    let dev = dev.inner.clone();
    let name = name.to_owned();

    let sock = TOKIO_RUNTIME.block_on(async move {
        let sock = dev.connect_by_name(&name, port).await?;

        ok_arc(TcpStream {
            inner: Arc::new(sock),
        })
    });

    erl_result(env, sock)
}

#[rustler::nif(schedule = "DirtyIo")]
fn tcp_accept(env: rustler::Env<'_>, sock: ResourceArc<TcpListener>) -> impl Encoder {
    let inner = sock.inner.clone();

    let sock = TOKIO_RUNTIME.block_on(async move {
        let stream = inner.accept().await?;

        ok_arc(TcpStream {
            inner: Arc::new(stream),
        })
    });

    erl_result(env, sock)
}

#[rustler::nif(schedule = "DirtyIo")]
fn tcp_send(env: rustler::Env, sock: ResourceArc<TcpStream>, msg: Vec<u8>) -> rustler::Term {
    let inner = sock.inner.clone();

    match TOKIO_RUNTIME.block_on(async move { inner.send(&msg).await }) {
        Ok(n) => (atoms::ok(), n).encode(env),
        Err(e) => (atoms::error(), e.to_string()).encode(env),
    }
}

#[rustler::nif(schedule = "DirtyIo")]
fn tcp_recv(env: rustler::Env, sock: ResourceArc<TcpStream>) -> impl Encoder {
    let inner = sock.inner.clone();

    let buf = TOKIO_RUNTIME.block_on(async move {
        let buf = inner.recv_bytes().await?;
        Result::<_>::Ok(buf.to_vec())
    });

    erl_result(env, buf)
}

#[rustler::nif]
fn tcp_local_addr(env: rustler::Env, sock: ResourceArc<TcpStream>) -> impl Encoder {
    crate::sockaddr_to_erl(env, sock.inner.local_addr())
}

#[rustler::nif]
fn tcp_remote_addr(env: rustler::Env, sock: ResourceArc<TcpStream>) -> impl Encoder {
    crate::sockaddr_to_erl(env, sock.inner.remote_addr())
}
