use axum::{
  Router,
  extract::{Path, State},
  http::{Extensions, StatusCode},
  response::{
    IntoResponse,
    Response,
    Sse,
    sse::{Event, KeepAlive},
  },
  routing::get,
};
use uuid::Uuid;

use crate::{
  error::ApiError,
  permissions::{self, Permission},
  state::AppState,
};

async fn get_visible_build(
  state: &AppState,
  id: Uuid,
  extensions: &Extensions,
) -> Result<circus_common::Build, ApiError> {
  let build = circus_common::repo::builds::get(&state.pool, id).await?;
  circus_common::repo::evaluations::get_visible(
    &state.pool,
    build.evaluation_id,
    permissions::check(extensions, Permission::Admin),
  )
  .await?;
  Ok(build)
}

async fn get_build_log(
  extensions: Extensions,
  State(state): State<AppState>,
  Path(id): Path<Uuid>,
) -> Result<Response, ApiError> {
  get_visible_build(&state, id, &extensions).await?;

  let log_storage = circus_common::log_storage::LogStorage::new(
    state.config.logs.log_dir.clone(),
  )
  .map_err(|e| ApiError(circus_common::CiError::Io(e)))?;

  let path = log_storage.log_path(&id);
  let Some(path) =
    crate::routes::canonical_log_file(&state.config.logs.log_dir, &path).await
  else {
    return Ok(
      (StatusCode::NOT_FOUND, "No log available for this build")
        .into_response(),
    );
  };

  match tokio::fs::read_to_string(path).await {
    Ok(content) => {
      Ok(
        (
          StatusCode::OK,
          [("content-type", "text/plain; charset=utf-8")],
          content,
        )
          .into_response(),
      )
    },
    Err(e) => Err(ApiError(circus_common::CiError::Io(e))),
  }
}

async fn stream_build_log(
  extensions: Extensions,
  State(state): State<AppState>,
  Path(id): Path<Uuid>,
) -> Result<
  Sse<impl futures::Stream<Item = Result<Event, std::convert::Infallible>>>,
  ApiError,
> {
  let build = get_visible_build(&state, id, &extensions).await?;

  let log_storage = circus_common::log_storage::LogStorage::new(
    state.config.logs.log_dir.clone(),
  )
  .map_err(|e| ApiError(circus_common::CiError::Io(e)))?;

  let active_path = log_storage.log_path_for_active(&id);
  let final_path = log_storage.log_path(&id);
  let log_dir = state.config.logs.log_dir.clone();
  let pool = state.pool.clone();
  let build_id = build.id;

  let stream = async_stream::stream! {
      use tokio::io::{AsyncBufReadExt, BufReader};

      // Determine which file to read
      let path = if active_path.exists() {
          active_path.clone()
      } else if final_path.exists() {
          final_path.clone()
      } else {
          // Wait for the file to appear
          let mut found = false;
          for _ in 0..30 {
              tokio::time::sleep(std::time::Duration::from_secs(1)).await;
              if active_path.exists() || final_path.exists() {
                  found = true;
                  break;
              }
          }
          if !found {
              yield Ok(Event::default().data("No log file available"));
              return;
          }
          if active_path.exists() { active_path.clone() } else { final_path.clone() }
      };

      let Some(path) = crate::routes::canonical_log_file(&log_dir, &path).await else {
        yield Ok(Event::default().data("Log file is unavailable"));
        return;
      };

      let Ok(file) = tokio::fs::File::open(&path).await else {
        yield Ok(Event::default().data("Failed to open log file"));
        return;
      };

      let mut reader = BufReader::new(file);
      let mut line = String::new();
      let mut consecutive_empty = 0u32;

      loop {
          line.clear();
          match reader.read_line(&mut line).await {
              Ok(0) => {
                  // EOF - check if build is still running
                  consecutive_empty += 1;
                  if consecutive_empty > 5 {
                      // Check build status
                      if let Ok(b) = circus_common::repo::builds::get(&pool, build_id).await
                          && b.status != circus_common::models::BuildStatus::Running
                              && b.status != circus_common::models::BuildStatus::Pending {
                              yield Ok(Event::default().event("done").data("Build completed"));
                              return;
                          }
                      consecutive_empty = 0;
                  }
                  tokio::time::sleep(std::time::Duration::from_millis(500)).await;
              }
              Ok(_) => {
                  consecutive_empty = 0;
                  yield Ok(Event::default().data(line.trim_end()));
              }
              Err(_) => return,
          }
      }
  };

  Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
}

pub fn router() -> Router<AppState> {
  Router::new()
    .route("/builds/{id}/log", get(get_build_log))
    .route("/builds/{id}/log/stream", get(stream_build_log))
}
