use crate::error::RariError;
use crate::runtime::JsExecutionRuntime;
use crate::runtime::bridge::RequestBridge;
use crate::server::routing::types::RouteSegment;
use axum::body::Body;
use axum::http::{HeaderMap, Request, Response};
use cow_utils::CowUtils;
use dashmap::DashMap;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::path::Path;
use std::sync::Arc;
use std::time::SystemTime;
use tracing::error;
use urlencoding::decode;

fn parse_decoded_path_segments(path: &str) -> Vec<String> {
    path.split('/')
        .filter(|s| !s.is_empty())
        .map(|s| decode(s).unwrap_or_else(|_| s.to_string().into()).into_owned())
        .collect()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiRouteEntry {
    pub path: String,
    #[serde(rename = "filePath")]
    pub file_path: String,
    pub segments: Vec<RouteSegment>,
    pub params: Vec<String>,
    #[serde(rename = "isDynamic")]
    pub is_dynamic: bool,
    pub methods: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiRouteManifest {
    #[serde(rename = "apiRoutes", default)]
    pub api_routes: Vec<ApiRouteEntry>,
}

#[derive(Debug, Clone)]
pub struct ApiRouteMatch {
    pub route: ApiRouteEntry,
    pub params: FxHashMap<String, String>,
    pub method: String,
}

#[derive(Debug, Clone)]
pub(crate) struct CompiledHandler {
    module_id: String,
    code: String,
    last_modified: SystemTime,
}

pub struct ApiRouteHandler {
    runtime: Arc<JsExecutionRuntime>,
    manifest: Arc<ApiRouteManifest>,
    handler_cache: Arc<DashMap<String, CompiledHandler>>,
}

impl ApiRouteHandler {
    pub fn new(runtime: Arc<JsExecutionRuntime>, manifest: ApiRouteManifest) -> Self {
        Self { runtime, manifest: Arc::new(manifest), handler_cache: Arc::new(DashMap::new()) }
    }

    pub async fn from_file(
        runtime: Arc<JsExecutionRuntime>,
        manifest_path: &str,
    ) -> Result<Self, RariError> {
        let content = tokio::fs::read_to_string(manifest_path)
            .await
            .map_err(|e| RariError::io(format!("Failed to read API route manifest: {e}")))?;

        let manifest: ApiRouteManifest = serde_json::from_str(&content).map_err(|e| {
            RariError::configuration(format!("Failed to parse API route manifest: {e}"))
        })?;

        Ok(Self::new(runtime, manifest))
    }

    pub fn manifest(&self) -> &ApiRouteManifest {
        &self.manifest
    }

    pub fn runtime(&self) -> &Arc<JsExecutionRuntime> {
        &self.runtime
    }

    pub fn clear_cache(&self) {
        self.handler_cache.clear();
    }

    pub fn invalidate_handler(&self, file_path: &str) {
        self.handler_cache.remove(file_path);
    }

    pub fn get_supported_methods(&self, path: &str) -> Option<Vec<String>> {
        let normalized_path = Self::normalize_path(path);

        for route in &self.manifest.api_routes {
            if self.match_route_pattern(route, &normalized_path).is_some() {
                return Some(route.methods.clone());
            }
        }

        None
    }

    pub fn match_route(&self, path: &str, method: &str) -> Result<ApiRouteMatch, RariError> {
        let normalized_path = Self::normalize_path(path);

        for route in &self.manifest.api_routes {
            if let Some(params) = self.match_route_pattern(route, &normalized_path) {
                if !route.methods.iter().any(|m| m.eq_ignore_ascii_case(method)) {
                    return Err(RariError::bad_request(format!(
                        "Method {} not allowed for route {}. Supported methods: {}",
                        method,
                        route.path,
                        route.methods.join(", ")
                    ))
                    .with_property("error_type", "method_not_allowed")
                    .with_property("allowed_methods", &route.methods.join(",")));
                }

                return Ok(ApiRouteMatch {
                    route: route.clone(),
                    params,
                    method: method.to_string(),
                });
            }
        }

        Err(RariError::not_found(format!("No API route found for path: {}", path)))
    }

    fn match_route_pattern(
        &self,
        route: &ApiRouteEntry,
        path: &str,
    ) -> Option<FxHashMap<String, String>> {
        let route_segments = route.path.split('/').filter(|s| !s.is_empty()).collect::<Vec<_>>();
        let path_segments = parse_decoded_path_segments(path);

        let mut params = FxHashMap::default();
        let mut route_idx = 0;
        let mut path_idx = 0;

        while route_idx < route_segments.len() {
            let route_seg = route_segments[route_idx];

            if route_seg.starts_with("[[...") && route_seg.ends_with("]]") {
                let param_name = &route_seg[5..route_seg.len() - 2];

                if path_idx < path_segments.len() {
                    let remaining: Vec<String> = path_segments[path_idx..].to_vec();
                    params.insert(param_name.to_string(), remaining.join("/"));
                }

                return Some(params);
            }

            if route_seg.starts_with("[...") && route_seg.ends_with(']') {
                let param_name = &route_seg[4..route_seg.len() - 1];

                if path_idx >= path_segments.len() {
                    return None;
                }

                let remaining: Vec<String> = path_segments[path_idx..].to_vec();
                params.insert(param_name.to_string(), remaining.join("/"));

                return Some(params);
            }

            if route_seg.starts_with('[') && route_seg.ends_with(']') {
                if path_idx >= path_segments.len() {
                    return None;
                }

                let param_name = &route_seg[1..route_seg.len() - 1];
                params.insert(param_name.to_string(), path_segments[path_idx].clone());

                path_idx += 1;
                route_idx += 1;
                continue;
            }

            if path_idx >= path_segments.len() || route_seg != path_segments[path_idx] {
                return None;
            }

            path_idx += 1;
            route_idx += 1;
        }

        if path_idx == path_segments.len() { Some(params) } else { None }
    }

    fn normalize_path(path: &str) -> String {
        let path = path.trim();

        let path = path.split('?').next().unwrap_or(path);
        let path = path.split('#').next().unwrap_or(path);

        if path.is_empty() || !path.starts_with('/') {
            format!("/{}", path)
        } else {
            path.to_string()
        }
    }

    pub(crate) async fn load_handler(
        &self,
        route: &ApiRouteEntry,
        is_development: bool,
    ) -> Result<CompiledHandler, RariError> {
        let file_path = &route.file_path;

        if let Some(cached) = self.handler_cache.get(file_path) {
            if is_development {
                let dist_path = Self::resolve_dist_path(file_path)?;
                if let Ok(metadata) = tokio::fs::metadata(&dist_path).await
                    && let Ok(modified) = metadata.modified()
                    && modified <= cached.last_modified
                {
                    return Ok(cached.clone());
                }
            } else {
                return Ok(cached.clone());
            }
        }

        let dist_path = Self::resolve_dist_path(file_path)?;

        if !dist_path.exists() {
            error!(
                file_path = %file_path,
                dist_path = %dist_path.display(),
                route_path = %route.path,
                "Handler file not found"
            );
            return Err(RariError::not_found(format!(
                "Handler file not found: {}",
                dist_path.display()
            )));
        }

        let code = tokio::fs::read_to_string(&dist_path).await.map_err(|e| {
            error!(
                file_path = %file_path,
                dist_path = %dist_path.display(),
                error = %e,
                "Failed to read handler file"
            );
            RariError::io(format!("Failed to read handler file: {e}"))
        })?;

        let last_modified = tokio::fs::metadata(&dist_path)
            .await
            .and_then(|m| m.modified())
            .unwrap_or_else(|_| SystemTime::now());

        let module_id = file_path
            .trim_start_matches("api/")
            .trim_end_matches(".ts")
            .trim_end_matches(".tsx")
            .trim_end_matches(".js")
            .trim_end_matches(".jsx")
            .cow_replace('/', "_");

        let compiled = CompiledHandler {
            module_id: module_id.into_owned(),
            code: code.clone(),
            last_modified,
        };

        self.handler_cache.insert(file_path.clone(), compiled.clone());

        Ok(compiled)
    }

    fn resolve_dist_path(file_path: &str) -> Result<std::path::PathBuf, RariError> {
        let mut normalized_path = String::new();
        let mut chars = file_path.chars().peekable();

        while let Some(ch) = chars.next() {
            if ch == '[' {
                if chars.peek() == Some(&'[') {
                    chars.next();

                    if chars.peek() == Some(&'.') {
                        chars.next();
                        if chars.peek() == Some(&'.') {
                            chars.next();
                            if chars.peek() == Some(&'.') {
                                chars.next();
                                normalized_path.push_str("_____");

                                while let Some(ch) = chars.next() {
                                    if ch == ']' && chars.peek() == Some(&']') {
                                        chars.next();
                                        normalized_path.push_str("__");
                                        break;
                                    } else {
                                        normalized_path.push(ch);
                                    }
                                }
                            }
                        }
                    }
                } else if chars.peek() == Some(&'.') {
                    chars.next();
                    if chars.peek() == Some(&'.') {
                        chars.next();
                        if chars.peek() == Some(&'.') {
                            chars.next();
                            normalized_path.push_str("____");
                            for ch in chars.by_ref() {
                                if ch == ']' {
                                    normalized_path.push('_');
                                    break;
                                } else {
                                    normalized_path.push(ch);
                                }
                            }
                        }
                    }
                } else {
                    normalized_path.push('_');
                    for ch in chars.by_ref() {
                        if ch == ']' {
                            normalized_path.push('_');
                            break;
                        } else {
                            normalized_path.push(ch);
                        }
                    }
                }
            } else {
                normalized_path.push(ch);
            }
        }

        let dist_path =
            Path::new("dist").join("server").join("app").join(normalized_path).with_extension("js");

        Ok(dist_path)
    }

    pub async fn execute_handler(
        &self,
        route_match: &ApiRouteMatch,
        request: Request<Body>,
        is_development: bool,
    ) -> Result<Response<Body>, RariError> {
        const MAX_API_BODY_SIZE: usize = 10 * 1024 * 1024;

        let handler = self.load_handler(&route_match.route, is_development).await.map_err(|e| {
            error!(
                route_path = %route_match.route.path,
                method = %route_match.method,
                error = %e,
                "Failed to load handler"
            );
            e
        })?;

        let (parts, body) = request.into_parts();

        let body_bytes = axum::body::to_bytes(body, MAX_API_BODY_SIZE).await.map_err(|e| {
            error!(
                route_path = %route_match.route.path,
                method = %route_match.method,
                error = %e,
                "Failed to read request body (may exceed size limit)"
            );
            RariError::bad_request(format!("Failed to read request body: {e}"))
        })?;

        let body_string = String::from_utf8_lossy(&body_bytes).to_string();

        let request_obj = self.create_request_object(
            parts.method.as_ref(),
            &parts.uri.to_string(),
            &parts.headers,
            &body_string,
            &route_match.params,
        )?;

        let request_context =
            std::sync::Arc::new(crate::server::middleware::request_context::RequestContext::new(
                route_match.route.path.clone(),
            ));

        self.runtime.set_request_context(request_context).await.map_err(|e| {
            error!(
                route_path = %route_match.route.path,
                method = %route_match.method,
                error = %e,
                "Failed to set request context"
            );
            RariError::js_execution(format!("Failed to set request context: {e}"))
        })?;

        let result = async {
            let dist_path = Self::resolve_dist_path(&route_match.route.file_path)?;
            let canonical_path = dist_path.canonicalize().map_err(|e| {
                RariError::io(format!(
                    "Failed to canonicalize API route path {}: {e}",
                    dist_path.display()
                ))
            })?;
            let module_specifier = url::Url::from_file_path(&canonical_path)
                .map_err(|_| {
                    RariError::configuration(format!(
                        "Failed to create file URL from path: {}",
                        canonical_path.display()
                    ))
                })?
                .to_string();

            if let Err(e) = self
                .runtime
                .add_module_to_loader_only(&module_specifier, handler.code.clone())
                .await
            {
                error!(
                    route_path = %route_match.route.path,
                    method = %route_match.method,
                    module_id = %handler.module_id,
                    error = %e,
                    "Failed to add API route module to loader"
                );
                return Err(RariError::js_execution(format!(
                    "Failed to add module to loader: {e}"
                )));
            }

            let component_id = dist_path
                .strip_prefix(Path::new("dist").join("server"))
                .map_err(|_| {
                    RariError::configuration(format!(
                        "Failed to derive component_id from dist path: {}",
                        dist_path.display()
                    ))
                })?
                .with_extension("")
                .to_string_lossy()
                .cow_replace('\\', "/")
                .into_owned();

            let module_id = self.runtime.load_es_module(&component_id).await.map_err(|e| {
                error!(
                    route_path = %route_match.route.path,
                    method = %route_match.method,
                    component_id = %component_id,
                    error = %e,
                    "Failed to load API route as ES module"
                );
                RariError::js_execution(format!("Failed to load ES module: {e}"))
            })?;

            if let Err(e) = self.runtime.evaluate_module(module_id).await {
                error!(
                    route_path = %route_match.route.path,
                    method = %route_match.method,
                    module_id = module_id,
                    error = %e,
                    "Failed to evaluate API route module"
                );
                return Err(RariError::js_execution(format!("Failed to evaluate module: {e}")));
            }

            let result = self
                .execute_handler_from_namespace(
                    &route_match.method,
                    &request_obj,
                    &module_specifier,
                )
                .await
                .map_err(|e| {
                    error!(
                        route_path = %route_match.route.path,
                        method = %route_match.method,
                        module_id = %handler.module_id,
                        error = %e,
                        "Handler execution failed"
                    );
                    RariError::js_execution(format!("Handler execution failed: {e}"))
                })?;

            let response = self.create_response(result).await.map_err(|e| {
                error!(
                    route_path = %route_match.route.path,
                    method = %route_match.method,
                    error = %e,
                    "Failed to create response from handler result"
                );
                e
            })?;

            Ok(response)
        }
        .await;

        if let Err(e) = self.runtime.clear_request_context().await {
            error!("Failed to clear request context: {}", e);
        }

        result
    }

    fn create_request_object(
        &self,
        method: &str,
        uri: &str,
        headers: &HeaderMap,
        body: &str,
        params: &FxHashMap<String, String>,
    ) -> Result<JsonValue, RariError> {
        RequestBridge::to_json(method, uri, headers, body, params)
    }

    async fn execute_handler_from_namespace(
        &self,
        method: &str,
        request_obj: &JsonValue,
        module_specifier: &str,
    ) -> Result<JsonValue, RariError> {
        let request_json = serde_json::to_string(request_obj)
            .map_err(|e| RariError::serialization(format!("Failed to serialize request: {e}")))?;

        let script = format!(
            r#"globalThis['~rari'].apiHandler.callHandler({}, "{}", "{}")"#,
            request_json,
            module_specifier.cow_replace('\\', "\\\\").cow_replace('"', "\\\""),
            method.cow_replace('\\', "\\\\").cow_replace('"', "\\\"")
        );

        let trimmed = module_specifier.trim_start_matches("file://");
        let with_underscores = trimmed.cow_replace('/', "_");
        let script_name = with_underscores.cow_replace(':', "_");

        self.runtime
            .execute_script(format!("api_route_call_{}", script_name), script)
            .await
            .map_err(|e| RariError::js_execution(format!("Failed to execute handler: {e}")))
    }

    async fn create_response(&self, result: JsonValue) -> Result<Response<Body>, RariError> {
        RequestBridge::from_json(result)
    }
}
