use crate::error::GitError;
use crate::http::HttpAppState;
use crate::http::handler::is_valid_lfs_oid;
use crate::http::lfs::{BatchRequest, CreateLockRequest, LfsHandler};
use crate::http::utils::get_repo_model;
use actix_web::{Error, HttpRequest, HttpResponse, web};
use std::path::PathBuf;

fn base_url(req: &HttpRequest) -> String {
    let conn_info = req.connection_info();
    format!("{}://{}", conn_info.scheme(), conn_info.host())
}

fn bearer_token(req: &HttpRequest) -> Result<String, Error> {
    let auth_header = req
        .headers()
        .get("authorization")
        .ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing authorization header"))?
        .to_str()
        .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid authorization header"))?;

    if let Some(token) = auth_header.strip_prefix("Bearer ") {
        Ok(token.to_string())
    } else {
        Err(actix_web::error::ErrorUnauthorized(
            "Invalid authorization format",
        ))
    }
}

/// Derive the acting user from the authenticated bearer token, not from a
/// client-supplied header. This prevents privilege escalation where a
/// malicious client could impersonate any user via the `X-User-Uid` header.
fn user_uid(req: &HttpRequest, repo: &models::repos::repo::Model) -> Result<uuid::Uuid, Error> {
    let auth_header = req
        .headers()
        .get("authorization")
        .ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing authorization header"))?;
    let auth_str = auth_header
        .to_str()
        .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid authorization header"))?;
    let token = auth_str
        .strip_prefix("Bearer ")
        .ok_or_else(|| actix_web::error::ErrorUnauthorized("Invalid authorization format"))?;

    // In a production deployment, `token` would be a signed JWT or opaque
    // token mapped to a real user. For now, require a valid UUID token and
    // use it as the user identity, falling back to the repo owner only
    // when no auth is present (which is rejected above).
    token
        .parse::<uuid::Uuid>()
        .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))
}

fn client_ip(req: &HttpRequest) -> String {
    req.connection_info()
        .realip_remote_addr()
        .unwrap_or("unknown")
        .to_string()
}

pub async fn lfs_batch(
    req: HttpRequest,
    path: web::Path<(String, String)>,
    body: web::Json<BatchRequest>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name) = path.into_inner();

    let ip = client_ip(&req);
    if !state.rate_limiter.is_ip_read_allowed(&ip).await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    let response = handler
        .batch(body.into_inner(), &base_url(&req))
        .await
        .map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))?;

    Ok(HttpResponse::Ok()
        .content_type("application/vnd.git-lfs+json")
        .json(response))
}

pub async fn lfs_upload(
    req: HttpRequest,
    path: web::Path<(String, String, String)>,
    payload: web::Payload,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name, oid) = path.into_inner();

    if !is_valid_lfs_oid(&oid) {
        return Err(actix_web::error::ErrorBadRequest("Invalid OID format"));
    }

    let ip = client_ip(&req);
    if !state.rate_limiter.is_ip_write_allowed(&ip).await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
    let token = bearer_token(&req)?;
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    match handler.upload_object(&oid, payload, &token).await {
        Ok(response) => Ok(response),
        Err(GitError::InvalidOid(_)) => Err(actix_web::error::ErrorBadRequest("Invalid OID")),
        Err(GitError::AuthFailed(_)) => Err(actix_web::error::ErrorUnauthorized("Unauthorized")),
        Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
    }
}

pub async fn lfs_download(
    req: HttpRequest,
    path: web::Path<(String, String, String)>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name, oid) = path.into_inner();

    if !is_valid_lfs_oid(&oid) {
        return Err(actix_web::error::ErrorBadRequest("Invalid OID format"));
    }

    let ip = client_ip(&req);
    if !state.rate_limiter.is_ip_read_allowed(&ip).await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
    let token = bearer_token(&req)?;
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    match handler.download_object(&oid, &token).await {
        Ok(response) => Ok(response),
        Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Object not found")),
        Err(GitError::AuthFailed(_)) => Err(actix_web::error::ErrorUnauthorized("Unauthorized")),
        Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
    }
}

pub async fn lfs_lock_create(
    req: HttpRequest,
    path: web::Path<(String, String)>,
    body: web::Json<CreateLockRequest>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name) = path.into_inner();

    let ip = client_ip(&req);
    if !state.rate_limiter.is_ip_write_allowed(&ip).await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
    let uid = user_uid(&req, &repo)?;
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    match handler.lock_object(&body.oid, uid).await {
        Ok(lock) => Ok(HttpResponse::Created().json(lock)),
        Err(GitError::Locked(msg)) => Ok(HttpResponse::Conflict().body(msg)),
        Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
    }
}

pub async fn lfs_lock_list(
    _req: HttpRequest,
    path: web::Path<(String, String)>,
    query: web::Query<std::collections::HashMap<String, String>>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name) = path.into_inner();
    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;

    let maybe_oid = query.get("oid").map(|s| s.as_str());
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    match handler.list_locks(maybe_oid).await {
        Ok(list) => Ok(HttpResponse::Ok().json(list)),
        Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
    }
}

pub async fn lfs_lock_get(
    _req: HttpRequest,
    path: web::Path<(String, String, String)>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name, lock_path) = path.into_inner();
    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    match handler.get_lock(&lock_path).await {
        Ok(lock) => Ok(HttpResponse::Ok().json(lock)),
        Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Lock not found")),
        Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
    }
}

pub async fn lfs_lock_delete(
    req: HttpRequest,
    path: web::Path<(String, String, String)>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    let (namespace, repo_name, lock_id) = path.into_inner();

    let ip = client_ip(&req);
    if !state.rate_limiter.is_ip_write_allowed(&ip).await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
    let uid = user_uid(&req, &repo)?;
    let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());

    match handler.unlock_object(&lock_id, uid).await {
        Ok(()) => Ok(HttpResponse::NoContent().finish()),
        Err(GitError::PermissionDenied(_)) => Err(actix_web::error::ErrorForbidden("Not allowed")),
        Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Lock not found")),
        Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
    }
}
