use crate::error::RoutePathError;
use serde_json::Value;

use super::join_paths;

/// Typed callback that adds one or more schemas into the OpenAPI registry.
pub type OpenApiSchemaRegistrar =
    fn(&mut Vec<(String, Value)>) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;

/// Static route metadata generated by route macros.
#[derive(Clone, Debug)]
pub struct RouteMetadata {
    method: &'static str,
    path: &'static str,
    summary: Option<&'static str>,
    tags: &'static [&'static str],
    response_status: Option<http::StatusCode>,
    request_schema: Option<&'static str>,
    response_schema: Option<&'static str>,
    request_schema_registrar: Option<OpenApiSchemaRegistrar>,
    response_schema_registrar: Option<OpenApiSchemaRegistrar>,
    guards: &'static [&'static str],
    pipes: &'static [&'static str],
    validates: bool,
}

impl RouteMetadata {
    /// Creates route metadata.
    pub const fn new(method: &'static str, path: &'static str) -> Self {
        Self {
            method,
            path,
            summary: None,
            tags: &[],
            response_status: None,
            request_schema: None,
            response_schema: None,
            request_schema_registrar: None,
            response_schema_registrar: None,
            guards: &[],
            pipes: &[],
            validates: false,
        }
    }

    /// Creates route metadata with an OpenAPI summary.
    pub const fn with_summary(
        method: &'static str,
        path: &'static str,
        summary: &'static str,
    ) -> Self {
        Self {
            method,
            path,
            summary: Some(summary),
            tags: &[],
            response_status: None,
            request_schema: None,
            response_schema: None,
            request_schema_registrar: None,
            response_schema_registrar: None,
            guards: &[],
            pipes: &[],
            validates: false,
        }
    }

    /// Creates route metadata with all supported static annotations.
    pub const fn with_annotations(
        method: &'static str,
        path: &'static str,
        summary: Option<&'static str>,
        guards: &'static [&'static str],
        pipes: &'static [&'static str],
        validates: bool,
    ) -> Self {
        Self {
            method,
            path,
            summary,
            tags: &[],
            response_status: None,
            request_schema: None,
            response_schema: None,
            request_schema_registrar: None,
            response_schema_registrar: None,
            guards,
            pipes,
            validates,
        }
    }

    /// Creates route metadata with all supported static annotations, including OpenAPI tags.
    pub const fn with_openapi_annotations(
        method: &'static str,
        path: &'static str,
        summary: Option<&'static str>,
        tags: &'static [&'static str],
        guards: &'static [&'static str],
        pipes: &'static [&'static str],
        validates: bool,
    ) -> Self {
        Self {
            method,
            path,
            summary,
            tags,
            response_status: None,
            request_schema: None,
            response_schema: None,
            request_schema_registrar: None,
            response_schema_registrar: None,
            guards,
            pipes,
            validates,
        }
    }

    /// Adds OpenAPI schema references to route metadata.
    pub const fn with_openapi_schemas(
        mut self,
        request_schema: Option<&'static str>,
        response_schema: Option<&'static str>,
    ) -> Self {
        self.request_schema = request_schema;
        self.response_schema = response_schema;
        self
    }

    /// Adds OpenAPI schema registration callbacks for route metadata.
    pub fn with_openapi_schema_registrars(
        mut self,
        request_schema: Option<OpenApiSchemaRegistrar>,
        response_schema: Option<OpenApiSchemaRegistrar>,
    ) -> Self {
        self.request_schema_registrar = request_schema;
        self.response_schema_registrar = response_schema;
        self
    }

    /// Adds an OpenAPI success response status to route metadata.
    pub const fn with_openapi_status(mut self, response_status: Option<http::StatusCode>) -> Self {
        self.response_status = response_status;
        self
    }

    /// Returns the HTTP method.
    pub const fn method(&self) -> &'static str {
        self.method
    }

    /// Returns the route path as declared by the user.
    pub const fn path(&self) -> &'static str {
        self.path
    }

    /// Returns the OpenAPI summary, when declared.
    pub const fn summary(&self) -> Option<&'static str> {
        self.summary
    }

    /// Returns OpenAPI tags declared on the route.
    pub const fn tags(&self) -> &'static [&'static str] {
        self.tags
    }

    /// Returns the OpenAPI success response status, when declared.
    pub const fn response_status(&self) -> Option<http::StatusCode> {
        self.response_status
    }

    /// Returns the OpenAPI request schema reference name, when declared.
    pub const fn request_schema(&self) -> Option<&'static str> {
        self.request_schema
    }

    /// Returns the OpenAPI response schema reference name, when declared.
    pub const fn response_schema(&self) -> Option<&'static str> {
        self.response_schema
    }

    /// Returns the OpenAPI request schema registration callback, when declared.
    pub const fn request_schema_registrar(&self) -> Option<OpenApiSchemaRegistrar> {
        self.request_schema_registrar
    }

    /// Returns the OpenAPI response schema registration callback, when declared.
    pub const fn response_schema_registrar(&self) -> Option<OpenApiSchemaRegistrar> {
        self.response_schema_registrar
    }

    /// Returns guard type names declared on the route.
    pub const fn guards(&self) -> &'static [&'static str] {
        self.guards
    }

    /// Returns pipe type names declared on the route.
    pub const fn pipes(&self) -> &'static [&'static str] {
        self.pipes
    }

    /// Returns whether validation is enabled for the route.
    pub const fn validates(&self) -> bool {
        self.validates
    }

    /// Composes this route path with a controller prefix using Nidus path normalization.
    pub fn full_path(&self, controller_prefix: &str) -> String {
        self.try_full_path(controller_prefix)
            .unwrap_or_else(|error| panic!("{error}"))
    }

    /// Tries to compose this route path with a controller prefix using Nidus path normalization.
    pub fn try_full_path(&self, controller_prefix: &str) -> Result<String, RoutePathError> {
        join_paths(controller_prefix, self.path)
    }
}
