use std::{path::PathBuf, time::Duration};

use actix_web::{Error, HttpRequest, HttpResponse, web};
use tokio::time::timeout;

use crate::{
    http::{
        HttpAppState, auth::authorize_repo_access, handler::GitHttpHandler,
        utils::get_repo_model,
    },
    sync::{
        RepoReceiveSyncTask,
        push_queue::{
            PushQueueEvent, PushQueueWaitError, wait_for_push_queue_slot,
        },
    },
};

pub async fn info_refs(
    req: HttpRequest,
    path: web::Path<(String, String)>,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    if !state.rate_limiter.is_read_allowed().await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let service_param = req
        .query_string()
        .split('&')
        .find(|s| s.starts_with("service="))
        .and_then(|s| s.strip_prefix("service="))
        .ok_or_else(|| {
            actix_web::error::ErrorBadRequest("Missing service parameter")
        })?;

    if service_param != "git-upload-pack" && service_param != "git-receive-pack"
    {
        return Err(actix_web::error::ErrorBadRequest("Invalid service"));
    }

    let path_inner = path.into_inner();
    let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
    let is_write = service_param == "git-receive-pack";
    authorize_repo_access(&req, &state.db, &model, is_write).await?;

    let storage_path = PathBuf::from(&model.storage_path);
    let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
    handler.info_refs(service_param).await
}

pub async fn upload_pack(
    req: HttpRequest,
    path: web::Path<(String, String)>,
    payload: web::Payload,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    if !state.rate_limiter.is_read_allowed().await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let path_inner = path.into_inner();
    let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
    authorize_repo_access(&req, &state.db, &model, false).await?;

    let storage_path = PathBuf::from(&model.storage_path);
    let handler = GitHttpHandler::new(storage_path, model, state.db.clone());
    handler.upload_pack(payload).await
}

pub async fn receive_pack(
    req: HttpRequest,
    path: web::Path<(String, String)>,
    payload: web::Payload,
    state: web::Data<HttpAppState>,
) -> Result<HttpResponse, Error> {
    if !state.rate_limiter.is_write_allowed().await {
        return Err(actix_web::error::ErrorTooManyRequests(
            "Rate limit exceeded",
        ));
    }

    let path_inner = path.into_inner();
    let model = get_repo_model(&path_inner.0, &path_inner.1, &state.db).await?;
    authorize_repo_access(&req, &state.db, &model, true).await?;

    let mut push_queue_lease = match wait_for_push_queue_slot(
        state.sync.clone(),
        model.id,
        |event, request_id| {
            let request_id = request_id.to_string();
            let repo_name = model.name.clone();
            let repo_id = model.id;
            match event {
                PushQueueEvent::Waiting(position) => {
                    tracing::info!(
                        repo = %repo_name,
                        repo_id = %repo_id,
                        request_id = %request_id,
                        position = position.position,
                        total = position.total,
                        "http_push_queue_waiting"
                    );
                }
                PushQueueEvent::Acquired => {
                    tracing::info!(
                        repo = %repo_name,
                        repo_id = %repo_id,
                        request_id = %request_id,
                        "http_push_queue_acquired"
                    );
                }
            }
        },
    )
    .await
    {
        Ok(lease) => lease,
        Err(PushQueueWaitError::Join(e)) => {
            tracing::error!(
                error = %e,
                repo = %model.name,
                repo_id = %model.id,
                "http_push_queue_join_failed"
            );
            return Err(actix_web::error::ErrorServiceUnavailable(
                "Push queue is temporarily unavailable. Please retry later.",
            ));
        }
        Err(PushQueueWaitError::Lock(e)) => {
            tracing::error!(
                error = %e,
                repo = %model.name,
                repo_id = %model.id,
                "http_push_queue_lock_failed"
            );
            return Err(actix_web::error::ErrorServiceUnavailable(
                "Push queue lock failed. Please retry later.",
            ));
        }
        Err(PushQueueWaitError::Timeout) => {
            tracing::info!(
                repo = %model.name,
                repo_id = %model.id,
                "http_push_queue_timeout"
            );
            return Ok(HttpResponse::ServiceUnavailable()
                .insert_header(("Retry-After", "5"))
                .content_type("text/plain; charset=utf-8")
                .body("Push queue timed out. Please retry in a moment.\n"));
        }
    };

    let storage_path = PathBuf::from(&model.storage_path);
    let handler =
        GitHttpHandler::new(storage_path, model.clone(), state.db.clone());
    let result = handler.receive_pack(payload).await;
    push_queue_lease.release().await;

    if result.is_ok() {
        let _ = tokio::spawn({
            let sync = state.sync.clone();
            let repo_uid = model.id;
            async move {
                let _ = timeout(
                    Duration::from_secs(5),
                    sync.send(RepoReceiveSyncTask { repo_uid }),
                )
                .await;
            }
        });
    }

    result
}
