use std::fmt::{
    Display,
    Formatter,
    Error as FmtError
};

use clap::ValueEnum;
use serde::{Serialize, Deserialize};


#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum CliRequest {
    GetAwsCredential {
        name: Option<String>,
        base: bool,
    },
    GetDockerCredential {
        server_url: String,
    },
    StoreDockerCredential(DockerCredential),
    EraseDockerCredential {
        server_url: String,
    },
    InvokeShortcut{
        action: ShortcutAction,
    },
}


#[derive(Debug, Copy, Clone, Serialize, Deserialize, ValueEnum)]
pub enum ShortcutAction {
    ShowWindow,
    LaunchTerminal,
}


#[derive(Debug, Serialize, Deserialize)]
pub enum CliResponse {
    Credential(CliCredential),
    Empty,
}

impl Display for CliResponse {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
        match self {
            CliResponse::Credential(CliCredential::AwsBase(_)) => write!(f, "Credential (AwsBase)"),
            CliResponse::Credential(CliCredential::AwsSession(_)) => write!(f, "Credential (AwsSession)"),
            CliResponse::Credential(CliCredential::Docker(_)) => write!(f, "Credential (Docker)"),
            CliResponse::Empty => write!(f, "Empty"),
        }
    }
}


#[derive(Debug, Serialize, Deserialize)]
pub enum CliCredential {
    AwsBase(AwsBaseCredential),
    AwsSession(AwsSessionCredential),
    Docker(DockerCredential),
}


#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AwsBaseCredential {
    #[serde(default = "default_aws_version")]
    pub version: usize,
    pub access_key_id: String,
    pub secret_access_key: String,
}


#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AwsSessionCredential {
    #[serde(default = "default_aws_version")]
    pub version: usize,
    pub access_key_id: String,
    pub secret_access_key: String,
    pub session_token: String,
    // we don't need to know the expiration for the CLI, so just use a string here
    pub expiration: String,
}


fn default_aws_version() -> usize { 1 }


#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct DockerCredential {
    #[serde(rename = "ServerURL")]
    pub server_url: String,
    pub username: String,
    pub secret: String,
}


#[derive(Debug, Serialize, Deserialize)]
pub struct ServerError {
    code: String,
    msg: String,
}

impl Display for ServerError {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
        write!(f, "Error response ({}) from server: {}", self.code, self.msg)?;
        Ok(())
    }
}

impl std::error::Error for ServerError {}
