//! Note route handlers

use axum::{
    Json,
    extract::{Path, Query, State},
    http::StatusCode,
};
use uuid::Uuid;
use validator::Validate;

use notes_domain::{
    CreateNoteRequest as DomainCreateNote, NoteTitle, TagName,
    UpdateNoteRequest as DomainUpdateNote,
};

use crate::error::{ApiError, ApiResult};
use crate::state::AppState;
use crate::{
    dto::{CreateNoteRequest, ListNotesQuery, NoteResponse, SearchQuery, UpdateNoteRequest},
    extractors::CurrentUser,
};

/// List notes with optional filtering
/// GET /api/v1/notes
pub async fn list_notes(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Query(query): Query<ListNotesQuery>,
) -> ApiResult<Json<Vec<NoteResponse>>> {
    let user_id = user.id;

    // Build the filter, looking up tag_id by name if needed
    let mut filter = notes_domain::NoteFilter::new();
    filter.is_pinned = query.pinned;
    filter.is_archived = query.archived;

    // Look up tag by name if provided
    if let Some(ref tag_name) = query.tag {
        if let Ok(Some(tag)) = state.tag_repo.find_by_name(user_id, tag_name).await {
            filter.tag_id = Some(tag.id);
        } else {
            // Tag not found, return empty results
            return Ok(Json(vec![]));
        }
    }

    let notes = state.note_service.list_notes(user_id, filter).await?;
    let response: Vec<NoteResponse> = notes.into_iter().map(NoteResponse::from).collect();

    Ok(Json(response))
}

/// Create a new note
/// POST /api/v1/notes
pub async fn create_note(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Json(payload): Json<CreateNoteRequest>,
) -> ApiResult<(StatusCode, Json<NoteResponse>)> {
    let user_id = user.id;

    // Validate input
    payload
        .validate()
        .map_err(|e| ApiError::validation(e.to_string()))?;

    // Parse title into NoteTitle (optional - empty string becomes None)
    let title: Option<NoteTitle> = if payload.title.trim().is_empty() {
        None
    } else {
        Some(
            NoteTitle::try_from(payload.title)
                .map_err(|e| ApiError::validation(format!("Invalid title: {}", e)))?,
        )
    };

    // Parse tags into TagName values
    let tags: Vec<TagName> = payload
        .tags
        .into_iter()
        .map(|s| {
            TagName::try_from(s).map_err(|e| ApiError::validation(format!("Invalid tag: {}", e)))
        })
        .collect::<Result<Vec<_>, _>>()?;

    let domain_req = DomainCreateNote {
        user_id,
        title,
        content: payload.content,
        tags,
        color: payload.color,
        is_pinned: payload.is_pinned,
    };

    let note = state.note_service.create_note(domain_req).await?;

    // Event publishing is now handled in NoteService via MessageBroker

    Ok((StatusCode::CREATED, Json(NoteResponse::from(note))))
}

/// Get a single note by ID
/// GET /api/v1/notes/:id
pub async fn get_note(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(id): Path<Uuid>,
) -> ApiResult<Json<NoteResponse>> {
    let user_id = user.id;

    let note = state.note_service.get_note(id, user_id).await?;

    Ok(Json(NoteResponse::from(note)))
}

/// Update a note
/// PATCH /api/v1/notes/:id
pub async fn update_note(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(id): Path<Uuid>,
    Json(payload): Json<UpdateNoteRequest>,
) -> ApiResult<Json<NoteResponse>> {
    let user_id = user.id;

    // Validate input
    payload
        .validate()
        .map_err(|e| ApiError::validation(e.to_string()))?;

    // Parse optional title - Some(string) -> Some(Some(NoteTitle)) or Some(None) for empty
    let title: Option<Option<NoteTitle>> = match payload.title {
        Some(t) if t.trim().is_empty() => Some(None), // Set title to None
        Some(t) => {
            Some(Some(NoteTitle::try_from(t).map_err(|e| {
                ApiError::validation(format!("Invalid title: {}", e))
            })?))
        }
        None => None, // Don't update title
    };

    // Parse optional tags
    let tags: Option<Vec<TagName>> = match payload.tags {
        Some(tag_strings) => Some(
            tag_strings
                .into_iter()
                .map(|s| {
                    TagName::try_from(s)
                        .map_err(|e| ApiError::validation(format!("Invalid tag: {}", e)))
                })
                .collect::<Result<Vec<_>, _>>()?,
        ),
        None => None,
    };

    let domain_req = DomainUpdateNote {
        id,
        user_id,
        title,
        content: payload.content,
        is_pinned: payload.is_pinned,
        is_archived: payload.is_archived,
        color: payload.color,
        tags,
    };

    let note = state.note_service.update_note(domain_req).await?;

    // Event publishing is now handled in NoteService via MessageBroker

    Ok(Json(NoteResponse::from(note)))
}

/// Delete a note
/// DELETE /api/v1/notes/:id
pub async fn delete_note(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(id): Path<Uuid>,
) -> ApiResult<StatusCode> {
    let user_id = user.id;

    state.note_service.delete_note(id, user_id).await?;

    Ok(StatusCode::NO_CONTENT)
}

/// Search notes
/// GET /api/v1/notes/search
pub async fn search_notes(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Query(query): Query<SearchQuery>,
) -> ApiResult<Json<Vec<NoteResponse>>> {
    let user_id = user.id;

    let notes = state.note_service.search_notes(user_id, &query.q).await?;
    let response: Vec<NoteResponse> = notes.into_iter().map(NoteResponse::from).collect();

    Ok(Json(response))
}

/// List versions of a note
/// GET /api/v1/notes/:id/versions
pub async fn list_note_versions(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(id): Path<Uuid>,
) -> ApiResult<Json<Vec<crate::dto::NoteVersionResponse>>> {
    let user_id = user.id;

    let versions = state.note_service.list_note_versions(id, user_id).await?;
    let response: Vec<crate::dto::NoteVersionResponse> = versions
        .into_iter()
        .map(crate::dto::NoteVersionResponse::from)
        .collect();

    Ok(Json(response))
}

/// Get related notes
/// GET /api/v1/notes/:id/related
/// Get related notes
/// GET /api/v1/notes/:id/related
#[cfg(feature = "smart-features")]
pub async fn get_related_notes(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(id): Path<Uuid>,
) -> ApiResult<Json<Vec<crate::dto::NoteLinkResponse>>> {
    let user_id = user.id;

    // Verify access to the source note
    state.note_service.get_note(id, user_id).await?;

    // Get links
    let links = state.link_repo.get_links_for_note(id).await?;
    let response: Vec<crate::dto::NoteLinkResponse> = links
        .into_iter()
        .map(crate::dto::NoteLinkResponse::from)
        .collect();

    Ok(Json(response))
}
