use clap::{Args, Parser, Subcommand};
use telnet::Telnet;

use crate::parameter::ParameterList;
use crate::models::{Channel, EventType};
use crate::models::Client;
use crate::utils::SendTextMessageTarget;
use crate::wrappers;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
    Channels(ChannelsArgs),
    Clients,
    Fetch(FetchArgs),
    Message(MessageArgs),
    Move(MoveArgs),
    Update(UpdateArgs),
    Events(EventArgs),
}

#[derive(Args)]
pub struct ChannelsArgs {
    #[arg(long)]
    pub spacers: bool,
}

#[derive(Args)]
pub struct FetchArgs {
    #[arg(long)]
    strict_client: bool,
    #[arg(long)]
    strict_channel: bool,
    #[arg(long)]
    channel: Option<String>,
    #[arg(long)]
    client: Option<String>,
}

#[derive(Args)]
pub struct MessageArgs {
    #[arg(long)]
    strict_client: bool,
    #[arg(long)]
    client: Option<String>,
    #[arg(long)]
    server: bool,
    pub message: String,
}

#[derive(Args)]
pub struct MoveArgs {
    #[arg(long)]
    strict_client: bool,
    #[arg(long)]
    strict_channel: bool,
    channel: String,
    client: Option<String>,
}

#[derive(Args)]
pub struct UpdateArgs {
    #[arg(long, short)]
    name: Option<String>,
    #[arg(long, short)]
    away: Option<String>,
    #[arg(long, short)]
    back: bool,
    #[arg(long, short)]
    microphone: Option<bool>,
    #[arg(long, short)]
    speakers: Option<bool>,
}

#[derive(Args)]
pub struct EventArgs {
    pub event: Vec<EventType>,
}

impl FetchArgs {
    pub fn want_channel(&self) -> bool {
        self.channel.is_some()
    }

    pub fn want_client(&self) -> bool {
        self.client.is_some()
    }

    pub fn channel(&self, connection: &mut Telnet) -> Result<Option<Channel>, String> {
        if let Some(channel) = &self.channel {
            wrappers::find_channel(connection, "channel_name", channel, self.strict_channel)
        } else {
            Err("No channel specified.".to_string())
        }
    }
    pub fn client(&self, connection: &mut Telnet) -> Result<Option<Client>, String> {
        if let Some(client) = &self.client {
            wrappers::find_client(connection, "client_nickname", client, self.strict_client)
        } else {
            Err("No client specified.".to_string())
        }
    }
}

impl MessageArgs {
    pub fn target(&self, connection: &mut Telnet) -> Result<SendTextMessageTarget, String> {
        if self.server {
            Ok(SendTextMessageTarget::Server)
        } else if let Some(client) = &self.client {
            if let Some(client) = wrappers::find_client(connection, "client_nickname", client, self.strict_client)? {
                return Ok(SendTextMessageTarget::Client(client));
            }
            return Err("Could not find client.".to_string());
        } else {
            Ok(SendTextMessageTarget::Channel)
        }
    }
}

impl MoveArgs {
    pub fn channel(&self, connection: &mut Telnet) -> Result<Option<Channel>, String> {
        wrappers::find_channel(connection, "channel_name", &self.channel, self.strict_channel)
    }
    pub fn client(&self, connection: &mut Telnet) -> Result<Option<Client>, String> {
        match &self.client {
            Some(client) => {
                wrappers::find_client(connection, "client_nickname", client, self.strict_client)
            }
            None => {
                match wrappers::find_self(connection) {
                    Ok(client) => Ok(Some(client)),
                    Err(msg) => Err(msg)
                }
            }
        }
    }
}

impl UpdateArgs {
    pub fn to_parameter_list(&self) -> ParameterList {
        let mut params: ParameterList = ParameterList::new();

        if let Some(name) = &self.name {
            params.insert(String::from("client_nickname"), name.clone());
        }

        if let Some(away) = &self.away {
            params.insert(String::from("client_away_message"), away.clone());
            params.insert(String::from("client_away"), String::from("1"));
        }
        if self.back {
            params.insert(String::from("client_away"), String::from("0"));
        }

        if let Some(microphone) = self.microphone {
            params.insert(String::from("client_input_muted"), u8::from(!microphone).to_string());
        }
        if let Some(speakers) = self.speakers {
            params.insert(String::from("client_output_muted"), u8::from(!speakers).to_string());
        }

        params
    }
}

pub fn init() -> Commands {
    Cli::parse().command
}