use axum::{
    extract::FromRequestParts,
    http::request::Parts,
    response::{IntoResponse as _, Redirect, Response},
};
use derive_builder::Builder;
use sqlx::postgres::types::Oid;
use uuid::Uuid;

use crate::{app::App, errors::AppError};

pub(crate) trait NavigatorPage {
    /// Returns the path component of the URL to the corresonding web page.
    /// Starts with a "/" character and the `root_path` if one is specified for
    /// [`crate::settings::Settings`].
    fn get_path(&self) -> String;

    /// Wrap [`Self::get_path`] in an Axum HTTP 303 redirection response for
    /// convenience.
    fn redirect_to(&self) -> Response {
        Redirect::to(&self.get_path()).into_response()
    }
}

/// Helper type for semantically generating URI paths, e.g. for redirects.
#[derive(Clone, Debug)]
pub(crate) struct Navigator {
    root_path: String,
    sub_path: String,
}

impl Navigator {
    pub(crate) fn workspace_page(&self, workspace_id: Uuid) -> Self {
        Self {
            sub_path: format!("/w/{0}/", workspace_id.simple()),
            ..self.clone()
        }
    }

    pub(crate) fn portal_page(&self) -> PortalPageBuilder {
        PortalPageBuilder {
            root_path: Some(self.get_root_path()),
            ..Default::default()
        }
    }

    pub(crate) fn form_page(&self, portal_id: Uuid) -> FormPageBuilder {
        FormPageBuilder {
            root_path: Some(self.get_root_path()),
            portal_id: Some(portal_id),
        }
    }

    /// Returns a [`NavigatorPage`] builder for navigating to a relation's
    /// "settings" page.
    pub(crate) fn rel_settings_page(&self) -> RelSettingsPageBuilder {
        RelSettingsPageBuilder {
            root_path: Some(self.get_root_path()),
            ..Default::default()
        }
    }

    pub(crate) fn get_root_path(&self) -> String {
        self.root_path.to_owned()
    }

    pub(crate) fn abs_path(&self) -> String {
        format!("{0}{1}", self.root_path, self.sub_path)
    }

    pub(crate) fn redirect_to(&self) -> Response {
        Redirect::to(&self.abs_path()).into_response()
    }
}

impl FromRequestParts<App> for Navigator {
    type Rejection = AppError;

    async fn from_request_parts(_: &mut Parts, state: &App) -> Result<Self, Self::Rejection> {
        Ok(Navigator {
            root_path: state.settings.root_path.clone(),
            sub_path: "/".to_owned(),
        })
    }
}

#[derive(Builder, Clone, Debug)]
pub(crate) struct PortalPage {
    portal_id: Uuid,

    rel_oid: Oid,

    #[builder(setter(custom))]
    root_path: String,

    /// Any value provided for `suffix` will be appended (without %-encoding) to
    /// the final path value. This may be used for sub-paths and/or search
    /// parameters.
    #[builder(default, setter(strip_option))]
    suffix: Option<String>,

    workspace_id: Uuid,
}

impl NavigatorPage for PortalPage {
    fn get_path(&self) -> String {
        format!(
            "{root_path}/w/{workspace_id}/r/{rel_oid}/p/{portal_id}/{suffix}",
            root_path = self.root_path,
            workspace_id = self.workspace_id.simple(),
            rel_oid = self.rel_oid.0,
            portal_id = self.portal_id.simple(),
            suffix = self.suffix.clone().unwrap_or_default()
        )
    }
}

#[derive(Builder, Clone, Debug)]
pub(crate) struct FormPage {
    portal_id: Uuid,

    #[builder(setter(custom))]
    root_path: String,
}

impl NavigatorPage for FormPage {
    fn get_path(&self) -> String {
        format!(
            "{root_path}/f/{portal_id}",
            root_path = self.root_path,
            portal_id = self.portal_id
        )
    }
}

#[derive(Builder, Clone, Debug)]
pub(crate) struct RelSettingsPage {
    rel_oid: Oid,

    #[builder(setter(custom))]
    root_path: String,

    /// Any value provided for `suffix` will be appended (without %-encoding) to
    /// the final path value. This may be used for sub-paths and/or search
    /// parameters.
    #[builder(default, setter(strip_option))]
    suffix: Option<String>,

    workspace_id: Uuid,
}

impl NavigatorPage for RelSettingsPage {
    fn get_path(&self) -> String {
        format!(
            "{root_path}/w/{workspace_id}/r/{rel_oid}/settings/{suffix}",
            root_path = self.root_path,
            workspace_id = self.workspace_id.simple(),
            rel_oid = self.rel_oid.0,
            suffix = self.suffix.clone().unwrap_or_default(),
        )
    }
}
