use std::io::Result;
use std::net::Ipv4Addr;

use async_std::task::block_on;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::join;
use tokio::net::{TcpListener, UdpSocket};
use hickory_proto::op::{Edns, Message, MessageType, Query, ResponseCode};
use hickory_proto::rr::{Name, RData, Record, RecordType, rdata::SOA, rdata::A, rdata::HTTPS};
use hickory_proto::rr::rdata::svcb::{SVCB, SvcParamKey, SvcParamValue, Alpn, IpHint};
use hickory_proto::serialize::binary::{BinDecodable, BinEncodable};
use url_domain_parse::utils::Domain;

const DNS_BUFFER_SIZE: usize = 1024;

pub fn run(domain_name: &str, host: &str, port: u16) {
    block_on(async {
        join!(udp(domain_name, host, port), tcp(domain_name, host, port));
    });
}

pub fn run_tls(_domain_name: &str, _host: &str, _port: u16, _key: &str, _cert: &str) {
    // TODO: run as cert.
}

async fn udp(domain_name: &str, host: &str, port: u16) {
    let socket = UdpSocket::bind(format!("{}:{}", host, port)).await.unwrap();

    loop {
        let mut buf = [0u8; DNS_BUFFER_SIZE];
        let recv_result = socket.recv_from(&mut buf).await;

        if recv_result.is_err() {
            continue;
        }

        // TODO: add tokio::spawn
        
        let (size, peer_addr) = recv_result.unwrap();
        let query = Message::from_bytes(&buf[..size]).unwrap();
        let response = handle_query(host, port, domain_name, query).await.unwrap();
        let data = response.to_bytes().unwrap();
        let result = socket.send_to(&data, peer_addr).await;

        if result.is_err() {
            continue;
        }

        result.unwrap();
    }
}

async fn tcp(domain_name: &str, host: &str, port: u16) {
    let listener = TcpListener::bind(format!("{}:{}", host, port)).await.unwrap();


    while let Ok((mut stream, _)) = listener.accept().await {
        let domain = String::from(domain_name);
        let address = String::from(host);
        let port = port.clone();

        tokio::spawn(async move {
            let mut buf = [0u8; DNS_BUFFER_SIZE];
            let result = stream.read(&mut buf).await;

            if result.is_err() {
                return;
            }

            let size = result.unwrap();
            let query = Message::from_bytes(&buf[..size]).unwrap();
            let response = handle_query(address.as_str(), port, &domain, query).await.unwrap();
            let data = response.to_bytes().unwrap();
            let result = stream.write(&data.to_bytes().unwrap()).await;

            if result.is_err() {
                return;
            }

            result.unwrap();
        });
    }
}

async fn handle_query(
    address: &str,
    port: u16,
    domain_name: &str,
    request: Message
) -> Result<Message> {
    let mut response = Message::new();

    response.set_id(request.id());
    response.set_message_type(MessageType::Response);
    response.set_op_code(request.op_code());
    response.set_authoritative(true);
    response.set_recursion_desired(request.recursion_desired());
    response.set_recursion_available(request.recursion_desired());
    response.set_response_code(ResponseCode::NoError);

    for query in request.queries() {
        let domain = Domain::parse(query.name().to_string().trim_end_matches("."));

        if domain.host.is_none() {
            response.set_response_code(ResponseCode::BADNAME);

            return Ok(response);
        }

        let mut response_edns = Edns::new();

        response_edns.set_max_payload(DNS_BUFFER_SIZE.try_into().unwrap());
        response.set_edns(response_edns);
        response.add_query(query.clone());
        response.set_response_code(ResponseCode::NoError);

        let search_result = search_dns_record(
            address,
            port,
            domain_name,
            &mut response,
            query
        ).await;

        if search_result.is_err() {
            response.set_response_code(ResponseCode::BADNAME);

            return Ok(response);
        }
    }

    Ok(response)
}

// TODO: check if domain match all host will be resolved :(
async fn search_dns_record(
    address: &str,
    port: u16,
    domain_name: &str,
    response: &mut Message,
    query: &Query
) -> Result<()> {
    match query.query_type() {
        RecordType::A => {
            response.add_answer(Record::from_rdata(
                query.name().clone(),
                60,
                RData::A(address.parse().unwrap()),
            ));
        }
        RecordType::AAAA => {
            response.add_name_server(Record::from_rdata(
                query.name().clone(),
                60,
                RData::SOA(SOA::new(
                    // Name::from_str_relaxed("ns1.tracker.com.").unwrap(),
                    Name::from_str_relaxed(format!("ns1.{}.", domain_name)).unwrap(),
                    // Name::from_str_relaxed("admin.tracker.com.").unwrap(),
                    Name::from_str_relaxed(format!("admin.{}.", domain_name)).unwrap(),
                    2023101001,
                    3600,
                    600,
                    86400,
                    60,
                )),
            ));
        }
        RecordType::ANAME => todo!(),
        RecordType::ANY => todo!(),
        RecordType::AXFR => todo!(),
        RecordType::CAA => todo!(),
        RecordType::CDS => todo!(),
        RecordType::CDNSKEY => todo!(),
        RecordType::CERT => todo!(),
        RecordType::CNAME => todo!(),
        RecordType::CSYNC => todo!(),
        RecordType::DNSKEY => todo!(),
        RecordType::DS => todo!(),
        RecordType::HINFO => todo!(),
        RecordType::HTTPS | RecordType::SVCB => {
            let params = vec![
                (
                    SvcParamKey::Alpn,
                    SvcParamValue::Alpn(Alpn(vec!["http/1.1".to_string()])),
                ),
                (SvcParamKey::Port, SvcParamValue::Port(port)),
                (
                    SvcParamKey::Ipv4Hint,
                    SvcParamValue::Ipv4Hint(IpHint(vec![A::from(address.parse::<Ipv4Addr>().unwrap())])),
                ),
            ];

            let svcb = SVCB::new(1, Name::root(), params);

            match query.query_type() {
                RecordType::HTTPS => response.add_answer(Record::from_rdata(query.name().clone(), 3600, RData::HTTPS(HTTPS(svcb)))),
                _ => response.add_answer(Record::from_rdata(query.name().clone(), 3600, RData::SVCB(svcb)))
            };
        }
        RecordType::IXFR => todo!(),
        RecordType::KEY => todo!(),
        RecordType::MX => todo!(),
        RecordType::NAPTR => todo!(),
        RecordType::NS => todo!(),
        RecordType::NSEC => todo!(),
        RecordType::NSEC3 => todo!(),
        RecordType::NSEC3PARAM => todo!(),
        RecordType::NULL => todo!(),
        RecordType::OPENPGPKEY => todo!(),
        RecordType::OPT => todo!(),
        RecordType::PTR => todo!(),
        RecordType::RRSIG => todo!(),
        RecordType::SIG => todo!(),
        RecordType::SOA => todo!(),
        RecordType::SRV => todo!(),
        RecordType::SSHFP => todo!(),
        RecordType::TLSA => todo!(),
        RecordType::TSIG => todo!(),
        RecordType::TXT => todo!(),
        RecordType::Unknown(_) => todo!(),
        RecordType::ZERO => todo!(),
        _ => {},
    };

    Ok(())
}