use std::{collections::HashMap, sync::Arc};

use axum::{
    body::Body,
    extract::{Path, Query, State},
    http::{Response, StatusCode},
};
use bytes::Bytes;
use tokio::sync::Mutex;
use tokio_stream::StreamExt as _;
use tracing::debug;

use crate::CodeState;

#[tracing::instrument(skip(state, repository))]
pub async fn info_refs(
    Path((_organization, repository)): Path<(String, String)>,
    Query(params): Query<HashMap<String, String>>,
    State(state): State<Arc<Mutex<CodeState>>>,
) -> Result<Response<Body>, StatusCode> {
    let state = state.lock().await;
    debug!("Received info refs request for repository: {}", repository);

    let repository_path = match state.get_repository(&repository) {
        Ok(path) => path,
        Err(_) => {
            debug!("Repository not found: {}", repository);
            return Ok(Response::builder()
                .status(StatusCode::NOT_FOUND)
                .body(Body::empty())
                .unwrap());
        }
    };

    let workspace = match state.load_repository(&repository_path) {
        Ok(workspace) => workspace,
        Err(_) => {
            debug!("Failed to get workspace for repository: {}", repository);
            return Ok(Response::builder()
                .status(StatusCode::INTERNAL_SERVER_ERROR)
                .body(Body::empty())
                .unwrap());
        }
    };

    let view = workspace.1.view();

    // Create a info refs response
    let mut info_refs = Vec::new();
    info_refs.push("# service=git-upload-pack\n".to_string());
    for (name, reference_target) in view.git_refs().iter() {
        let Some(commit_id) = reference_target.as_normal() else {
            continue;
        };

        debug!("Found reference: {} -> {}", name, commit_id.to_string());

        let mut line = String::new();
        line.push_str(commit_id.to_string().as_str());
        line.push(' ');
        line.push_str(name.as_str());
        line.push('\n');
        info_refs.push(line);
    }

    let body = info_refs
        .iter()
        .map(|line| {
            let mut hex_line = String::new();
            hex_line.push_str(&format!("{:06x}", line.len()));
            hex_line.push_str(line);
            hex_line
        })
        .collect::<Vec<_>>()
        .join("")
        + "0000";

    let response = Response::builder()
        .header(
            "Content-Type",
            "application/x-git-upload-pack-advertisement",
        )
        .header("Cache-Control", "no-cache")
        .header("Pragma", "no-cache")
        .header("Expires", "0")
        .body(Body::from(body))
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(response)
}