use std::time::Duration;

use telnet::Event::TimedOut;
use telnet::Telnet;

use crate::{commands, parameter};
use crate::parameter::{parameter_list_find_all, ParameterList};
use crate::models::Channel;
use crate::models::Client;
use crate::response::Response;
use crate::utils::SendTextMessageTarget;

pub fn skip_welcome(connection: &mut Telnet) -> Result<(), String> {
    loop {
        let event_result = connection.read_timeout(Duration::from_millis(100));
        match event_result {
            Ok(event) => {
                if let TimedOut = event {
                    return Ok(());
                }
            }
            Err(_) => return Err(String::from("Failed to read from Teamspeak.")),
        }
    }
}

pub fn login(connection: &mut Telnet) -> Result<(), String> {
    // read api key from environment variable
    let apikey = std::env::var("TS3_CLIENT_API_KEY")
        .map_err(|_| String::from("No API key found in environment variable TS3_CLIENT_API_KEY."))?;

    commands::login(connection, &apikey)
        .map(|_| ())
        .map_err(|err| format!("Failed to authenticate with Teamspeak: {}", err))
}

pub fn get_channels(connection: &mut Telnet, spacers: bool) -> Result<Vec<Channel>, String> {
    match commands::channellist(connection)? {
        Response::DataList(parameter_lists) => {
            let channels: Vec<Channel> = parameter_lists.iter()
                .map(|params| Channel::from(params.clone()))
                .collect();
            let mut channels = Channel::sort_list(channels);
            if !spacers {
                channels.retain(|c| !c.is_spacer());
            }
            Ok(channels)
        }
        _ => Err(String::from("Received unexpected models from Teamspeak."))
    }
}

pub fn find_channel(connection: &mut Telnet, key: &str, value: &str, strict: bool) -> Result<Option<Channel>, String> {
    match commands::channellist(connection)? {
        Response::DataList(parameter_lists) => {
            match parameter::parameter_list_find(&parameter_lists, key, value, strict) {
                Some(params) => {
                    Ok(Some(Channel::from(params)))
                }
                None => {
                    Ok(None)
                }
            }
        }
        _ => Err(String::from("Received unexpected models from Teamspeak."))
    }
}

pub fn get_clients(connection: &mut Telnet) -> Result<Vec<Client>, String> {
    match commands::clientlist(connection)? {
        Response::DataList(parameter_lists) => {
            let mut clients: Vec<Client> = parameter_lists.iter()
                .map(|params| Client::from(params.clone()))
                .collect();

            clients.sort_by(|a, b| a.client_nickname.cmp(&b.client_nickname));

            Ok(clients)
        }
        _ => Err(String::from("Received unexpected models from Teamspeak."))
    }
}

pub fn find_client(connection: &mut Telnet, key: &str, value: &str, strict: bool) -> Result<Option<Client>, String> {
    match commands::clientlist(connection)? {
        Response::DataList(parameter_lists) => {
            match parameter::parameter_list_find(&parameter_lists, key, value, strict) {
                Some(params) => {
                    Ok(Some(Client::from(params)))
                }
                None => {
                    Ok(None)
                }
            }
        }
        _ => Err(String::from("Received unexpected models from Teamspeak."))
    }
}

pub fn get_channel_clients(connection: &mut Telnet, channel: &Channel) -> Result<Vec<Client>, String> {
    match commands::clientlist(connection)? {
        Response::DataList(parameter_lists) => {
            let mut clients: Vec<Client> = Vec::new();
            for client_params in parameter_list_find_all(&parameter_lists, "cid", &channel.cid.to_string(), true) {
                clients.push(Client::from(client_params));
            }
            Ok(clients)
        }
        _ => Err(String::from("Received unexpected models from Teamspeak."))
    }
}

pub fn get_self_clid(connection: &mut Telnet) -> Result<String, String> {
    match commands::whoami(connection)? {
        Response::Data(params) => {
            match parameter::parameter_find(&params, "clid") {
                None => Err(String::from("Could not find clid in models from Teamspeak.")),
                Some(param) => Ok(param)
            }
        }
        _ => Err(String::from("Received unexpected models from Teamspeak for whoami."))
    }
}

pub fn find_self(connection: &mut Telnet) -> Result<Client, String> {
    let clid = get_self_clid(connection)?;

    match commands::clientlist(connection)? {
        Response::DataList(parameter_lists) => {
            match parameter::parameter_list_find(&parameter_lists, "clid", &clid, false) {
                Some(params) => {
                    Ok(Client::from(params))
                }
                None => {
                    Err(String::from("Could not find self in models from Teamspeak."))
                }
            }
        }
        _ => Err(String::from("Received unexpected models from Teamspeak for clientlist."))
    }
}

pub fn fetch_client(connection: &mut Telnet, clients: &[Client]) -> Result<Response, String> {
    let cid = find_self(connection)?.cid;
    let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect();

    commands::clientmove(connection, &cid, clid_list)
}

pub fn fetch_channel(connection: &mut Telnet, channel: Channel) -> Result<Response, String> {
    let cid = find_self(connection)?.cid;

    let clients = get_channel_clients(connection, &channel)?;
    let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect();

    commands::clientmove(connection, &cid, clid_list)
}

pub fn move_client(connection: &mut Telnet, channel: &Channel, clients: &[Client]) -> Result<Response, String> {
    let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect();

    commands::clientmove(connection, &channel.cid, clid_list)
}

pub fn update_client(connection: &mut Telnet, parameters: ParameterList) -> Result<Response, String> {
    commands::clientupdate(connection, parameters)
}

pub fn send_text_message(connection: &mut Telnet, target: SendTextMessageTarget, msg: String) -> Result<Response, String> {
    commands::sendtextmessage(connection, target, msg)
}