use actix_cors::Cors;
use actix_files::Files;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse};
use actix_web::{http::header, web, App, HttpResponse, HttpServer};
use futures::future::LocalBoxFuture;
use log::info;
use std::path::PathBuf;
use std::task::{Context, Poll};
use std::time::Instant;

/// Static file server for avatar, blob, and other static files
/// Serves files from /data/{type} directories

#[derive(Clone)]
struct StaticConfig {
    root: PathBuf,
    cors_enabled: bool,
}

impl StaticConfig {
    fn from_env() -> Self {
        let root = std::env::var("STATIC_ROOT").unwrap_or_else(|_| "/data".to_string());
        let cors = std::env::var("STATIC_CORS").unwrap_or_else(|_| "true".to_string());

        Self {
            root: PathBuf::from(root),
            cors_enabled: cors == "true" || cors == "1",
        }
    }

    fn ensure_dir(&self, name: &str) -> PathBuf {
        let dir = self.root.join(name);
        if !dir.exists() {
            std::fs::create_dir_all(&dir).ok();
        }
        dir
    }
}

async fn health() -> HttpResponse {
    HttpResponse::Ok().json(serde_json::json!({
        "status": "ok",
        "service": "static-server"
    }))
}

/// Custom middleware that logs requests except for noisy paths (health, metrics, static files).
struct RequestLogger;

impl<S, B> actix_web::dev::Transform<S, ServiceRequest> for RequestLogger
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = actix_web::Error;
    type Transform = RequestLoggerService<S>;
    type InitError = ();
    type Future = futures::future::Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        futures::future::ok(RequestLoggerService {
            service,
            _marker: std::marker::PhantomData,
        })
    }
}

struct RequestLoggerService<S> {
    service: S,
    _marker: std::marker::PhantomData<fn(ServiceRequest)>,
}

impl<S, B> Service<ServiceRequest> for RequestLoggerService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = actix_web::Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let path = req.path().to_string();
        let method = req.method().to_string();
        let should_log = !(path == "/health"
            || path == "/metrics"
            || path.starts_with("/ws")
            || path.starts_with("/avatar")
            || path.starts_with("/blob")
            || path.starts_with("/media")
            || path.starts_with("/static"));

        let start = Instant::now();
        let fut = self.service.call(req);

        Box::pin(async move {
            let res = fut.await?;
            if should_log {
                info!(
                    target: "static_server",
                    "{} {} {} {:?}",
                    method,
                    path,
                    res.status().as_u16(),
                    start.elapsed()
                );
            }
            Ok(res)
        })
    }
}

#[actix_web::main]
async fn main() -> anyhow::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    let cfg = StaticConfig::from_env();
    let bind = std::env::var("STATIC_BIND").unwrap_or_else(|_| "0.0.0.0:8081".to_string());

    println!("Static file server starting...");
    println!("  Root: {:?}", cfg.root);
    println!("  Bind: {}", bind);
    println!("  CORS: {}", if cfg.cors_enabled { "enabled" } else { "disabled" });

    // Ensure all directories exist
    for name in ["avatar", "blob", "media", "static"] {
        let dir = cfg.ensure_dir(name);
        println!("  {} dir: {:?}", name, dir);
    }

    let root = cfg.root.clone();
    let cors_enabled = cfg.cors_enabled;

    HttpServer::new(move || {
        let root = root.clone();

        let cors = if cors_enabled {
            Cors::default()
                .allow_any_origin()
                .allowed_methods(vec!["GET", "HEAD", "OPTIONS"])
                .allowed_headers(vec![
                    header::AUTHORIZATION,
                    header::ACCEPT,
                    header::CONTENT_TYPE,
                ])
                .max_age(3600)
        } else {
            Cors::permissive()
        };

        App::new()
            .wrap(cors)
            .wrap(RequestLogger)
            .route("/health", web::get().to(health))
            .service(
                Files::new("/avatar", root.join("avatar"))
                    .prefer_utf8(true)
                    .index_file("index.html"),
            )
            .service(
                Files::new("/blob", root.join("blob"))
                    .prefer_utf8(true)
                    .index_file("index.html"),
            )
            .service(
                Files::new("/media", root.join("media"))
                    .prefer_utf8(true)
                    .index_file("index.html"),
            )
            .service(
                Files::new("/static", root.join("static"))
                    .prefer_utf8(true)
                    .index_file("index.html"),
            )
    })
    .bind(&bind)?
    .run()
    .await?;

    Ok(())
}
