use std::{
    any::Any,
    fmt::{self, Debug},
    future::Future,
    pin::Pin,
    sync::Arc,
};

use http::Method;

use crate::{app::App, ctx::Ctx, into_response::IntoResponse};

pub type DynHandlerRun = Arc<dyn HandlerRun>;

pub enum HandlerType {
    Middleware,
    Method(Method),
}

impl fmt::Display for HandlerType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            HandlerType::Middleware => write!(f, "Middleware"),
            HandlerType::Method(method) => write!(f, "Method({method})"),
        }
    }
}

#[allow(non_snake_case)]
pub trait Handler<Ctx> {
    type Output;
    fn call(&self, c: Ctx) -> impl Future<Output = Self::Output> + Send;

    #[allow(unused_variables)]
    fn on_app_listen_mut(&self, app: &mut App) {}

    #[allow(unused_variables)]
    fn on_app_listen_arc(&self, app: &Arc<App>) {}

    fn state(&self) -> &dyn std::any::Any {
        &()
    }

    fn name(&self) -> &str {
        let full = std::any::type_name::<Self>();
        full.rsplit("::").next().unwrap_or(full)
    }
}

impl<F: ?Sized, Fut, Ctx> Handler<Ctx> for F
where
    F: Fn(Ctx) -> Fut,
    Fut: Future + Send,
{
    type Output = Fut::Output;
    fn call(&self, c: Ctx) -> impl Future<Output = Self::Output> + Send {
        (self)(c)
    }
}

pub(crate) struct HandlerWrapper<F> {
    pub(crate) f: F,
    pub(crate) handler_type: HandlerType,
    #[cfg(debug_assertions)]
    pub(crate) location: String,
}

impl<F> HandlerWrapper<F> {
    pub(crate) fn new(f: F, handler_type: HandlerType, _skip: usize) -> Self {
        Self {
            f,
            handler_type,
            #[cfg(debug_assertions)]
            location: caller_location(_skip),
        }
    }
}

impl<F: for<'a> Handler<&'a mut Ctx>> Debug for HandlerWrapper<F> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.handler_type {
            HandlerType::Middleware => write!(f, "{}({})", self.handler_type, self.f.name())?,
            HandlerType::Method(_) => write!(f, "{}", self.handler_type)?,
        }
        #[cfg(debug_assertions)]
        write!(f, " @ {}", self.location)?;
        Ok(())
    }
}

pub trait HandlerRun: Send + Sync + Debug {
    fn run<'s, 'c, 'a>(&'s self, c: &'c mut Ctx) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
    where
        's: 'a,
        'c: 'a,
        Self: 'a;

    fn handler_type(&self) -> &HandlerType;
    fn state(&self) -> &dyn Any;

    fn on_app_listen_mut(&self, _: &mut crate::app::App);
    fn on_app_listen_arc(&self, _: &Arc<crate::app::App>);

    fn type_id(&self) -> std::any::TypeId
    where
        Self: 'static;
}

impl dyn HandlerRun {
    pub fn get_state<S: 'static>(&self) -> Option<&S> {
        self.state().downcast_ref::<(S,)>().map(|s| &s.0)
    }
}

impl<F, R> HandlerRun for HandlerWrapper<F>
where
    F: for<'a> Handler<&'a mut Ctx, Output = R> + Send + Sync + 'static,
    R: IntoResponse + Send,
{
    fn run<'s, 'c, 'a>(&'s self, c: &'c mut Ctx) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
    where
        's: 'a,
        'c: 'a,
        Self: 'a,
    {
        Box::pin(async move {
            self.f.call(c).await.into_response(c);
        })
    }

    fn handler_type(&self) -> &HandlerType {
        &self.handler_type
    }

    fn state(&self) -> &dyn Any {
        self.f.state()
    }

    fn on_app_listen_mut(&self, a: &mut App) {
        self.f.on_app_listen_mut(a);
    }

    fn on_app_listen_arc(&self, a: &Arc<App>) {
        self.f.on_app_listen_arc(a);
    }

    fn type_id(&self) -> std::any::TypeId {
        self.f.type_id()
    }
}

#[cfg(debug_assertions)]
fn caller_location(skip: usize) -> String {
    let bt = std::backtrace::Backtrace::force_capture();
    let s = format!("{bt:?}");

    s.split("{ fn: ")
        .filter_map(|chunk| {
            let file_start = chunk.find("file: \"")?;
            let file_end = chunk[file_start + 7..].find('"')?;
            let file = &chunk[file_start + 7..file_start + 7 + file_end];

            let line_start = chunk.find("line: ")?;
            let line_end = chunk[line_start + 6..].find(' ')?;
            let line = &chunk[line_start + 6..line_start + 6 + line_end];

            Some(format!("{file}:{line}"))
        })
        .nth(skip)
        .unwrap_or_default()
}
