use crate::error::Error;
use crate::process::name::jsonschema;
use crate::{schema::Schema, scope::SchemaNamingStrategy, scope::SchemaScope, tools};
use serde_json::Value;

use super::endpoint;

pub struct OpenapiNamer;

pub struct OpenapiNamerOptions {
    pub resource_method_version: bool,
    pub overwrite: bool,
    pub overwrite_ambiguous: bool,
    pub naming_strategy: SchemaNamingStrategy,
}

impl OpenapiNamer {
    pub fn options() -> OpenapiNamerOptions {
        OpenapiNamerOptions {
            resource_method_version: false,
            overwrite: false,
            overwrite_ambiguous: false,
            naming_strategy: SchemaNamingStrategy::Default,
        }
    }
}

impl OpenapiNamerOptions {
    pub fn with_overwrite(&mut self, value: bool) -> &mut Self {
        self.overwrite = value;
        self
    }

    pub fn with_overwrite_ambiguous(&mut self, value: bool) -> &mut Self {
        self.overwrite_ambiguous = value;
        self
    }

    pub fn with_resource_method_version(&mut self, value: bool) -> &mut Self {
        self.resource_method_version = value;
        self
    }

    pub fn with_naming_strategy(&mut self, value: SchemaNamingStrategy) -> &mut Self {
        self.naming_strategy = value;
        self
    }

    pub fn process(&self, schema: &mut Schema) -> Result<(), Error> {
        let root = schema.get_body_mut();

        let mut scope = SchemaScope::new(self.naming_strategy.clone());

        tools::each_node_mut(
            root,
            &mut scope,
            "/any:components/any:schemas/definition:*",
            |node, parts, ctx| {
                if let [key] = parts {
                    ctx.glue(key);

                    jsonschema::name_schema(
                        node,
                        ctx,
                        &jsonschema::NamerOptions {
                            overwrite: self.overwrite,
                            overwrite_ambiguous: self.overwrite_ambiguous,
                            base_name: None,
                        },
                    )?;

                    ctx.pop();
                }

                Ok(())
            },
        )?;

        tools::each_node_mut(
            root,
            &mut scope,
            "/any:components/any:responses/definition:*/any:content/any:*/any:schema",
            |node, parts, ctx| {
                if let [key, _] = parts {
                    ctx.glue(key).glue("response");

                    jsonschema::name_schema(
                        node,
                        ctx,
                        &jsonschema::NamerOptions {
                            overwrite: self.overwrite,
                            overwrite_ambiguous: self.overwrite_ambiguous,
                            base_name: None,
                        },
                    )?;

                    ctx.reduce(2);
                }

                Ok(())
            },
        )?;

        tools::each_node_mut(
            root,
            &mut scope,
            "/any:components/any:requestBodies/definition:*/any:content/any:*/any:schema",
            |node, parts, ctx| {
                if let [key, _] = parts {
                    ctx.glue(key).glue("request");

                    jsonschema::name_schema(
                        node,
                        ctx,
                        &jsonschema::NamerOptions {
                            overwrite: self.overwrite,
                            overwrite_ambiguous: self.overwrite_ambiguous,
                            base_name: None,
                        },
                    )?;

                    ctx.reduce(2);
                }

                Ok(())
            },
        )?;

        tools::each_node_mut(
            root,
            &mut scope,
            "/path:paths/any:*/any:*",
            |node, parts, ctx| {
                if let [endpoint, method] = parts {
                    let details = node.as_object_mut().unwrap();

                    match endpoint::Endpoint::new(method.to_string(), endpoint.to_string()) {
                        Ok(endpoint) => {
                            let operation_id =
                                endpoint.get_operation_id(self.resource_method_version);

                            if !details.contains_key("operationId") || self.overwrite {
                                log::debug!("{}/operationId -> {}", ctx, operation_id);
                                details
                                    .insert("operationId".to_string(), Value::String(operation_id));
                            } else {
                                log::debug!("{}/operationId -> using original", ctx);
                            }
                        }
                        Err(e) => log::error!(
                            "/paths/{}/{}: cannot parse endpoint: {}",
                            endpoint,
                            method,
                            e
                        ),
                    }
                }

                Ok(())
            },
        )?;

        Ok(())
    }
}
