
use std::{str::FromStr as _, time::Duration};

use url::Url;
use utils::{binary::checksum, serialize::Serialize as _};

use crate::{API_DEFAULT_PORT, message::Message};

#[derive(Debug)]
pub enum UrlError {
    ParseError(url::ParseError),
    DomainBasePortError,
}

pub fn fix_url(url: &str) -> Result<String, UrlError> {
    let mut url: String = url.to_string();

    if !url.starts_with("http://") && !url.starts_with("https://") {
        url = format!("http://{}", url);
    }

    let mut url = Url::from_str(&url).map_err(|x| UrlError::ParseError(x))?;

    if url.port().is_none() {
        url.set_port(Some(API_DEFAULT_PORT)).map_err(|_| UrlError::DomainBasePortError)?;
    }

    let mut url = url.to_string();

    if !url.ends_with('/') {
        url.push('/');
    }

    Ok(url)
}

#[derive(Debug)]
pub enum ConnectionError {
    UrlError(UrlError),
    ReqwestError(reqwest::Error),
    StatusCodeError(u16),
}

pub fn tx_message(host: &str, password: &str, timeout_sec: u64, mut message: Message) -> Result<(), ConnectionError> {
    message.sanitize();

    let url = fix_url(host)
        .map_err(|x| ConnectionError::UrlError(x))?;

    let mut bytes = message.serialize_checked();
    utils::aes::encrypt_cbc(&mut bytes, &password);
    bytes.push(checksum(&bytes));

    let str = utils::binary::bin2hex(bytes);

    let resp = reqwest::blocking::Client::new()
        .post(url)
        .body(str)
        .timeout(Duration::from_secs(timeout_sec))
        .send()
        .map_err(|e| ConnectionError::ReqwestError(e))?;

    if resp.status().is_success() {
        Ok(())
    } else {
        Err(ConnectionError::StatusCodeError(resp.status().as_u16()))
    }
}

pub fn rx_messages(host: &str, password: &str, timeout_sec: u64) -> Result<Vec<Message>, ConnectionError> {
    let url = fix_url(host)
        .map_err(|x| ConnectionError::UrlError(x))?;

    let resp = reqwest::blocking::Client::new()
        .get(url)
        .timeout(Duration::from_secs(timeout_sec))
        .send()
        .map_err(|x| ConnectionError::ReqwestError(x))?;

    if resp.status() != reqwest::StatusCode::OK {
        return Err(ConnectionError::StatusCodeError(resp.status().as_u16()));
    }

    let bytes = resp.bytes()
        .map_err(|e| ConnectionError::ReqwestError(e))?;

    let contents = String::from_utf8_lossy(&bytes);
    let mut messages = vec![];

    for message_ser_hex in contents.split(",") {
        let Some(mut message_ser_bin) = utils::binary::hex2bin(message_ser_hex) else {
            continue;
        };

        if message_ser_bin.len() < 2 {
            continue;
        }

        // Checksum before decryption
        if checksum(&message_ser_bin[..message_ser_bin.len()-1]) != message_ser_bin[message_ser_bin.len()-1] {
            continue;
        }

        // Remove checksum
        message_ser_bin.pop();

        if utils::aes::decrypt_cbc(&mut message_ser_bin, &password).is_err() {
            continue;
        }

        let Ok(mut message) = Message::deserialize_checked(message_ser_bin) else {
            continue;
        };

        if message.is_valid() {
            message.sanitize();
            messages.push(message);
        }
    }

    Ok(messages)
}
