use std::fmt::{self, Display};

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
};
use clap::{Parser, Subcommand};
use uuid::Uuid;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ResourceState {
    Parsed(bool),
    Unparsed,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Icon {
    pub name: String,
    pub path: Option<String>,
    pub url: String,
    pub width: u32,
    pub height: u32,
    pub content_type: String,
}

impl std::default::Default for Icon {
    fn default() -> Self {
        Icon {
            name: String::new(),
            path: None,
            url: String::new(),
            width: 0,
            height: 0,
            content_type: String::new(),
        }
    }
}
impl Clone for Icon {
    fn clone(&self) -> Self {
        Icon {
            name: self.name.clone(),
            path: self.path.clone(),
            url: self.url.clone(),
            width: self.width,
            height: self.height,
            content_type: self.content_type.clone(),
        }
    }
}

impl Display for Icon {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Icon {{ name: {}, url: {}, width: {}, height: {}, content_type: {} }}",
            self.name, self.url, self.width, self.height, self.content_type
        )
    }
}

#[derive(Debug, Eq, Clone)]
pub struct Resource {
    pub name: String,
    pub title: String,
    pub url: Option<String>,
    pub icon: Option<Icon>,
    pub state: ResourceState,
}

impl Resource {
    pub fn new(name: String, title: String, url: Option<String>, icon: Option<Icon>) -> Self {
        Resource {
            name,
            title,
            url,
            icon,
            state: ResourceState::Unparsed,
        }
    }

    pub fn from_resource_with_quantity(resource: ResourceWithQuantity) -> Self {
        Resource {
            name: resource.name,
            title: resource.title,
            url: resource.url,
            icon: resource.icon,
            state: resource.state,
        }
    }
}
impl PartialEq for Resource {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
            && self.title == other.title
            && self.url == other.url
            && self.icon == other.icon
            && self.state == other.state
    }
}

impl PartialEq<&ResourceWithQuantity> for Resource {
    fn eq(&self, other: &&ResourceWithQuantity) -> bool {
        self.name == other.name
            && self.title == other.title
            && self.url == other.url
            && self.icon == other.icon
            && self.state == other.state
    }
}

#[derive(Debug, Eq, Clone)]
pub struct ResourceWithQuantity {
    pub name: String,
    pub title: String,
    pub url: Option<String>,
    pub icon: Option<Icon>,
    pub state: ResourceState,
    pub quantity: u32,
}

impl ResourceWithQuantity {
    pub fn from_resource(resource: Resource, quantity: u32) -> Self {
        ResourceWithQuantity {
            name: resource.name,
            title: resource.title,
            url: resource.url,
            icon: resource.icon,
            state: resource.state,
            quantity,
        }
    }

    pub fn from_ingredient(ingredient: Ingredient) -> Self {
        ResourceWithQuantity {
            name: ingredient.resource.name,
            title: ingredient.resource.title,
            url: ingredient.resource.url,
            icon: ingredient.resource.icon,
            state: ingredient.resource.state,
            quantity: ingredient.quantity,
        }
    }

    pub fn new(
        name: String,
        title: String,
        url: Option<String>,
        icon: Option<Icon>,
        quantity: u32,
    ) -> Self {
        ResourceWithQuantity {
            name,
            title,
            url,
            icon,
            state: ResourceState::Unparsed,
            quantity,
        }
    }

    pub fn to_resource(&self) -> Resource {
        Resource {
            name: self.name.clone(),
            title: self.title.clone(),
            url: self.url.clone(),
            icon: self.icon.clone(),
            state: self.state.clone(),
        }
    }
}
impl PartialEq for ResourceWithQuantity {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
            && self.title == other.title
            && self.url == other.url
            && self.icon == other.icon
            && self.state == other.state
            && self.quantity == other.quantity
    }
}

impl PartialEq<&Resource> for ResourceWithQuantity {
    fn eq(&self, other: &&Resource) -> bool {
        self.name == other.name
            && self.title == other.title
            && self.url == other.url
            && self.icon == other.icon
            && self.state == other.state
    }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ingredient {
    pub resource: Resource,
    pub quantity: u32,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RecipeType {
    Production,
    Refining,
    Cooking,
}

impl RecipeType {
    pub fn from_str(s: &str) -> Option<RecipeType> {
        match s {
            "production" => Some(RecipeType::Production),
            "refining" => Some(RecipeType::Refining),
            "cooking" => Some(RecipeType::Cooking),
            _ => None,
        }
    }

    pub fn as_string(&self) -> String {
        match self {
            RecipeType::Production => String::from("production"),
            RecipeType::Refining => String::from("refining"),
            RecipeType::Cooking => String::from("cooking"),
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            RecipeType::Production => "production",
            RecipeType::Refining => "refining",
            RecipeType::Cooking => "cooking",
        }
    }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Duration {
    pub millis: u64,
    pub unit: String,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Recipe {
    pub recipe_type: RecipeType,
    pub resource: ResourceWithQuantity,
    pub duration: Duration,
    pub ingredients: Vec<Ingredient>,
}

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct CallArguments {
    #[arg(short, long, default_value_t = false)]
    pub server: bool,
    #[command(subcommand)]
    pub client_commands: Option<ClientCommands>,
}

#[derive(Subcommand, Debug)]
pub enum ClientCommands {
    /// Adds files to myapp
    Add {
        name: String,
    },
    Get {
        id_or_name: String,
    },
}

pub struct ServerError(anyhow::Error);

// Tell axum how to convert `AppError` into a response.
impl IntoResponse for ServerError {
    fn into_response(self) -> Response {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Error: {}", self.0),
        )
            .into_response()
    }
}

impl<E> From<E> for ServerError
where
    E: Into<anyhow::Error>,
{
    fn from(err: E) -> Self {
        Self(err.into())
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum IdOrNone {
    NEW(Uuid),
    EXISTING(Uuid),
    NONE,
}

impl fmt::Display for IdOrNone {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            IdOrNone::NEW(id) => write!(f, "+{}", id.urn().to_string()),
            IdOrNone::EXISTING(id) => write!(f, "{}", id.urn().to_string()),
            IdOrNone::NONE => write!(f, "None"),
        }
    }
}

impl IdOrNone {
    pub fn to_id(&self) -> ID {
        match self {
            IdOrNone::NEW(id) => ID::NEW(*id),
            IdOrNone::EXISTING(id) => ID::EXISTING(*id),
            _ => panic!("Cannot convert ID to IdOrNone"),
        }
    }

    pub fn from_id(id: ID) -> IdOrNone {
        match id {
            ID::NEW(id) => IdOrNone::NEW(id),
            ID::EXISTING(id) => IdOrNone::EXISTING(id),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ID {
    NEW(Uuid),
    EXISTING(Uuid),
}

impl fmt::Display for ID {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ID::NEW(id) => write!(f, "+{}", id.urn().to_string()),
            ID::EXISTING(id) => write!(f, "{}", id.urn().to_string()),
        }
    }
}

impl ID {
    pub fn to_id_none(&self) -> IdOrNone {
        match self {
            ID::NEW(id) => IdOrNone::NEW(*id),
            ID::EXISTING(id) => IdOrNone::EXISTING(*id),
        }
    }

    pub fn from_id(id: IdOrNone) -> ID {
        match id {
            IdOrNone::NEW(id) => ID::NEW(id),
            IdOrNone::EXISTING(id) => ID::EXISTING(id),
            IdOrNone::NONE => panic!("Cannot convert ID::NONE to EXISTING_ID"),
        }
    }

    pub fn as_uuid(&self) -> Uuid {
        match self {
            ID::NEW(id) => *id,
            ID::EXISTING(id) => *id,
        }
    }
}
