//! Request Handler
//!
//! Handles incoming HTTP requests by routing based on Host header
//! and fetching content from Synor Storage.

use super::GatewayState;
use crate::error::Error;
use axum::{
    body::Body,
    extract::{Host, State},
    http::{header, Request, Response, StatusCode, Uri},
    response::IntoResponse,
};
use std::sync::Arc;

/// Content types by extension
const CONTENT_TYPES: &[(&str, &str)] = &[
    (".html", "text/html; charset=utf-8"),
    (".htm", "text/html; charset=utf-8"),
    (".css", "text/css; charset=utf-8"),
    (".js", "application/javascript; charset=utf-8"),
    (".mjs", "application/javascript; charset=utf-8"),
    (".json", "application/json; charset=utf-8"),
    (".xml", "application/xml; charset=utf-8"),
    (".svg", "image/svg+xml"),
    (".png", "image/png"),
    (".jpg", "image/jpeg"),
    (".jpeg", "image/jpeg"),
    (".gif", "image/gif"),
    (".webp", "image/webp"),
    (".avif", "image/avif"),
    (".ico", "image/x-icon"),
    (".woff", "font/woff"),
    (".woff2", "font/woff2"),
    (".ttf", "font/ttf"),
    (".otf", "font/otf"),
    (".eot", "application/vnd.ms-fontobject"),
    (".wasm", "application/wasm"),
    (".pdf", "application/pdf"),
    (".zip", "application/zip"),
    (".txt", "text/plain; charset=utf-8"),
    (".md", "text/markdown; charset=utf-8"),
    (".mp4", "video/mp4"),
    (".webm", "video/webm"),
    (".mp3", "audio/mpeg"),
    (".ogg", "audio/ogg"),
];

/// Request handler trait
pub trait RequestHandler {
    fn handle(&self, host: &str, path: &str) -> impl std::future::Future<Output = Response<Body>>;
}

/// Handle incoming request
pub async fn handle_request(
    State(state): State<Arc<GatewayState>>,
    Host(host): Host,
    request: Request<Body>,
) -> impl IntoResponse {
    let path = request.uri().path();
    let client_ip = request
        .headers()
        .get("x-forwarded-for")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("unknown");

    // Rate limiting
    if !state.rate_limiter.check(client_ip) {
        return rate_limited_response();
    }

    // Log request
    if state.config.enable_logging {
        eprintln!("[{}] {} {}", client_ip, host, path);
    }

    // Route the request
    let router = state.router.read().await;
    match router.route(&host, path) {
        Ok(resolved) => {
            // Fetch content from storage gateway
            match fetch_content(&state, &resolved.cid.to_string(), &resolved.path).await {
                Ok((content, original_content_type)) => {
                    let content_type = if original_content_type.is_some() {
                        original_content_type.unwrap()
                    } else {
                        guess_content_type(&resolved.path).to_string()
                    };

                    let mut builder = Response::builder()
                        .status(StatusCode::OK)
                        .header(header::CONTENT_TYPE, content_type);

                    // Add custom headers from route config
                    for (key, value) in &resolved.headers {
                        builder = builder.header(key.as_str(), value.as_str());
                    }

                    // Add cache headers
                    if resolved.is_fallback {
                        // SPA fallback - don't cache
                        builder = builder.header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate");
                    } else if is_immutable_asset(&resolved.path) {
                        // Hashed assets - cache forever
                        builder = builder.header(header::CACHE_CONTROL, "public, max-age=31536000, immutable");
                    } else {
                        // Regular assets - cache with revalidation
                        builder = builder.header(
                            header::CACHE_CONTROL,
                            format!("public, max-age={}", state.config.cache_ttl),
                        );
                    }

                    // Security headers
                    builder = builder
                        .header("X-Content-Type-Options", "nosniff")
                        .header("X-Frame-Options", "SAMEORIGIN")
                        .header("Referrer-Policy", "strict-origin-when-cross-origin");

                    builder.body(Body::from(content)).unwrap()
                }
                Err(e) => {
                    eprintln!("Error fetching content: {}", e);
                    not_found_response(&host)
                }
            }
        }
        Err(Error::Redirect { to, status }) => {
            redirect_response(&to, status)
        }
        Err(Error::NameNotFound(_)) | Err(Error::UnknownHost(_)) => {
            not_found_response(&host)
        }
        Err(Error::DomainNotVerified(domain)) => {
            domain_not_verified_response(&domain)
        }
        Err(e) => {
            eprintln!("Routing error: {}", e);
            error_response(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
        }
    }
}

/// Fetch content from storage gateway
async fn fetch_content(
    state: &GatewayState,
    cid: &str,
    path: &str,
) -> Result<(Vec<u8>, Option<String>), Error> {
    let url = format!("{}/{}{}", state.config.storage_gateway_url, cid, path);

    let response = state
        .http_client
        .get(&url)
        .send()
        .await
        .map_err(|e| Error::Storage(e.to_string()))?;

    if !response.status().is_success() {
        return Err(Error::Storage(format!(
            "Storage returned status {}",
            response.status()
        )));
    }

    let content_type = response
        .headers()
        .get(header::CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());

    let bytes = response
        .bytes()
        .await
        .map_err(|e| Error::Storage(e.to_string()))?;

    Ok((bytes.to_vec(), content_type))
}

/// Guess content type from path extension
fn guess_content_type(path: &str) -> &'static str {
    for (ext, content_type) in CONTENT_TYPES {
        if path.ends_with(ext) {
            return content_type;
        }
    }
    "application/octet-stream"
}

/// Check if path looks like an immutable asset (with hash in filename)
fn is_immutable_asset(path: &str) -> bool {
    // Common patterns: main.abc123.js, styles.abc123.css, image.abc123.png
    let parts: Vec<&str> = path.split('.').collect();
    if parts.len() >= 3 {
        let potential_hash = parts[parts.len() - 2];
        // Check if it looks like a hash (8+ hex or alphanumeric chars)
        potential_hash.len() >= 8
            && potential_hash.chars().all(|c| c.is_ascii_alphanumeric())
    } else {
        false
    }
}

/// Create a rate limited response
fn rate_limited_response() -> Response<Body> {
    Response::builder()
        .status(StatusCode::TOO_MANY_REQUESTS)
        .header(header::CONTENT_TYPE, "application/json")
        .header("Retry-After", "60")
        .body(Body::from(r#"{"error":"Rate limit exceeded"}"#))
        .unwrap()
}

/// Create a not found response
fn not_found_response(host: &str) -> Response<Body> {
    let body = format!(
        r#"<!DOCTYPE html>
<html>
<head>
    <title>404 - Not Found</title>
    <style>
        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
               display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;
               background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }}
        .container {{ text-align: center; padding: 40px; }}
        h1 {{ font-size: 72px; margin: 0; }}
        p {{ font-size: 18px; opacity: 0.8; }}
        a {{ color: white; text-decoration: underline; }}
    </style>
</head>
<body>
    <div class="container">
        <h1>404</h1>
        <p>The site <strong>{}</strong> was not found.</p>
        <p><a href="https://synor.cc">Deploy your own site on Synor</a></p>
    </div>
</body>
</html>"#,
        host
    );

    Response::builder()
        .status(StatusCode::NOT_FOUND)
        .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
        .body(Body::from(body))
        .unwrap()
}

/// Create a redirect response
fn redirect_response(to: &str, status: u16) -> Response<Body> {
    let status_code = StatusCode::from_u16(status).unwrap_or(StatusCode::TEMPORARY_REDIRECT);

    Response::builder()
        .status(status_code)
        .header(header::LOCATION, to)
        .body(Body::empty())
        .unwrap()
}

/// Create a domain not verified response
fn domain_not_verified_response(domain: &str) -> Response<Body> {
    let body = format!(
        r#"<!DOCTYPE html>
<html>
<head>
    <title>Domain Not Verified</title>
    <style>
        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
               display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;
               background: #f5f5f5; color: #333; }}
        .container {{ text-align: center; padding: 40px; max-width: 500px; }}
        h1 {{ font-size: 24px; color: #e74c3c; }}
        p {{ font-size: 16px; line-height: 1.6; }}
        code {{ background: #eee; padding: 2px 6px; border-radius: 4px; }}
    </style>
</head>
<body>
    <div class="container">
        <h1>Domain Not Verified</h1>
        <p>The domain <strong>{}</strong> has not been verified.</p>
        <p>If you own this domain, please complete DNS verification in your Synor dashboard.</p>
    </div>
</body>
</html>"#,
        domain
    );

    Response::builder()
        .status(StatusCode::FORBIDDEN)
        .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
        .body(Body::from(body))
        .unwrap()
}

/// Create an error response
fn error_response(status: StatusCode, message: &str) -> Response<Body> {
    Response::builder()
        .status(status)
        .header(header::CONTENT_TYPE, "application/json")
        .body(Body::from(format!(r#"{{"error":"{}"}}"#, message)))
        .unwrap()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_guess_content_type() {
        assert_eq!(guess_content_type("/index.html"), "text/html; charset=utf-8");
        assert_eq!(guess_content_type("/style.css"), "text/css; charset=utf-8");
        assert_eq!(guess_content_type("/app.js"), "application/javascript; charset=utf-8");
        assert_eq!(guess_content_type("/logo.png"), "image/png");
        assert_eq!(guess_content_type("/unknown.xyz"), "application/octet-stream");
    }

    #[test]
    fn test_is_immutable_asset() {
        assert!(is_immutable_asset("/main.abc12345.js"));
        assert!(is_immutable_asset("/styles.def67890.css"));
        assert!(!is_immutable_asset("/index.html"));
        assert!(!is_immutable_asset("/app.js"));
        assert!(!is_immutable_asset("/image.png"));
    }
}
