use std::collections::HashSet;

use anyhow::Result;
use axum::extract::{Path as AxumPath, Query, State};
use axum::response::Response;

use crate::db::{self, Store, TaskId};
use crate::score;

use super::helpers::{
    error_response, friendly_date, friendly_status, list_projects_safe, render, render_markdown,
    sort_tasks, SortField, SortOrder,
};
use super::views::{
    BlockerRef, IndexTemplate, LogView, ProjectCard, ProjectTemplate, ScoredEntry, SortContext,
    TaskRow, TaskTemplate, TaskView,
};
use super::AppState;

const PAGE_SIZE: usize = 25;

pub(super) async fn index_handler(State(state): State<AppState>) -> Response {
    let root = state.data_root.clone();
    let result = tokio::task::spawn_blocking(move || -> Result<IndexTemplate> {
        let projects = list_projects_safe(&root);
        let mut cards = Vec::with_capacity(projects.len());

        for name in &projects {
            match Store::open(&root, name) {
                Ok(store) => {
                    let tasks = store.list_tasks()?;
                    let open = tasks
                        .iter()
                        .filter(|t| t.status == db::Status::Open)
                        .count();
                    let in_progress = tasks
                        .iter()
                        .filter(|t| t.status == db::Status::InProgress)
                        .count();
                    let closed = tasks
                        .iter()
                        .filter(|t| t.status == db::Status::Closed)
                        .count();
                    cards.push(ProjectCard::Ok {
                        name: name.clone(),
                        open,
                        in_progress,
                        closed,
                        total: tasks.len(),
                    });
                }
                Err(e) => {
                    cards.push(ProjectCard::Err {
                        name: name.clone(),
                        error: format!("{e}"),
                    });
                }
            }
        }

        Ok(IndexTemplate {
            all_projects: projects,
            active_project: None,
            projects: cards,
        })
    })
    .await;

    match result {
        Ok(Ok(tmpl)) => render(tmpl),
        Ok(Err(e)) => error_response(500, &format!("{e}"), &[]),
        Err(e) => error_response(500, &format!("join error: {e}"), &[]),
    }
}

#[derive(serde::Deserialize)]
pub(super) struct ProjectQuery {
    status: Option<String>,
    priority: Option<String>,
    effort: Option<String>,
    label: Option<String>,
    q: Option<String>,
    page: Option<usize>,
    sort: Option<String>,
    order: Option<String>,
}

pub(super) async fn project_handler(
    State(state): State<AppState>,
    AxumPath(name): AxumPath<String>,
    Query(mut query): Query<ProjectQuery>,
) -> Response {
    // Default to showing open tasks when no status filter is specified.
    if query.status.is_none() {
        query.status = Some("open".to_string());
    }
    let root = state.data_root.clone();
    let result = tokio::task::spawn_blocking(move || -> Result<ProjectTemplate> {
        let all_projects = list_projects_safe(&root);
        let store = Store::open(&root, &name)?;
        let tasks = store.list_tasks()?;

        // Stats from the full unfiltered set.
        let stats_open = tasks
            .iter()
            .filter(|t| t.status == db::Status::Open)
            .count();
        let stats_in_progress = tasks
            .iter()
            .filter(|t| t.status == db::Status::InProgress)
            .count();
        let stats_closed = tasks
            .iter()
            .filter(|t| t.status == db::Status::Closed)
            .count();

        // Collect distinct labels for the filter dropdown.
        let mut label_set: HashSet<String> = HashSet::new();
        for t in &tasks {
            for l in &t.labels {
                label_set.insert(l.clone());
            }
        }
        let mut all_labels: Vec<String> = label_set.into_iter().collect();
        all_labels.sort();

        // Next-up scoring (top 5 open tasks).
        let open_tasks: Vec<score::TaskInput> = tasks
            .iter()
            .filter(|t| t.status == db::Status::Open)
            .map(|t| score::TaskInput {
                id: t.id.as_str().to_string(),
                title: t.title.clone(),
                priority_score: t.priority.score(),
                effort_score: t.effort.score(),
                priority_label: db::priority_label(t.priority).to_string(),
                effort_label: db::effort_label(t.effort).to_string(),
            })
            .collect();

        let edges: Vec<(String, String)> = tasks
            .iter()
            .filter(|t| t.status == db::Status::Open)
            .flat_map(|t| {
                t.blockers
                    .iter()
                    .map(|b| (t.id.as_str().to_string(), b.as_str().to_string()))
                    .collect::<Vec<_>>()
            })
            .collect();

        let parents_with_open_children: HashSet<String> = tasks
            .iter()
            .filter(|t| t.status == db::Status::Open)
            .filter_map(|t| t.parent.as_ref().map(|p| p.as_str().to_string()))
            .collect();

        let scored = score::rank(
            &open_tasks,
            &edges,
            &parents_with_open_children,
            score::Mode::Impact,
            5,
        );

        let next_up: Vec<ScoredEntry> = scored
            .into_iter()
            .map(|s| ScoredEntry {
                short_id: TaskId::display_id(&s.id),
                id: s.id,
                title: s.title,
                score: format!("{:.2}", s.score),
                status: "open".to_string(),
                status_display: friendly_status("open"),
            })
            .collect();

        // Apply filters.
        let mut filtered: Vec<&db::Task> = tasks.iter().collect();

        if let Some(ref s) = query.status {
            if !s.is_empty() {
                if let Ok(parsed) = db::parse_status(s) {
                    filtered.retain(|t| t.status == parsed);
                }
            }
        }
        if let Some(ref p) = query.priority {
            if !p.is_empty() {
                if let Ok(parsed) = db::parse_priority(p) {
                    filtered.retain(|t| t.priority == parsed);
                }
            }
        }
        if let Some(ref e) = query.effort {
            if !e.is_empty() {
                if let Ok(parsed) = db::parse_effort(e) {
                    filtered.retain(|t| t.effort == parsed);
                }
            }
        }
        if let Some(ref l) = query.label {
            if !l.is_empty() {
                filtered.retain(|t| t.labels.iter().any(|x| x == l));
            }
        }
        let search_term = query.q.clone().unwrap_or_default();
        if !search_term.is_empty() {
            let q = search_term.to_ascii_lowercase();
            filtered.retain(|t| t.title.to_ascii_lowercase().contains(&q));
        }

        // Sort: user-selected column, or priority+created as default.
        let sort_field = query
            .sort
            .as_deref()
            .and_then(SortField::parse)
            .unwrap_or(SortField::Priority);
        let sort_order = query
            .order
            .as_deref()
            .and_then(SortOrder::parse)
            .unwrap_or_else(|| sort_field.default_order());
        sort_tasks(&mut filtered, sort_field, sort_order);

        // Pagination.
        let total = filtered.len();
        let total_pages = if total == 0 {
            1
        } else {
            total.div_ceil(PAGE_SIZE)
        };
        let page = query.page.unwrap_or(1).clamp(1, total_pages);
        let start = (page - 1) * PAGE_SIZE;
        let end = (start + PAGE_SIZE).min(total);

        let page_tasks: Vec<TaskRow> = filtered[start..end]
            .iter()
            .map(|t| {
                let status = db::status_label(t.status).to_string();
                TaskRow {
                    full_id: t.id.as_str().to_string(),
                    short_id: t.id.short(),
                    status_display: friendly_status(&status),
                    status,
                    priority: db::priority_label(t.priority).to_string(),
                    effort: db::effort_label(t.effort).to_string(),
                    title: t.title.clone(),
                    created_at_display: friendly_date(&t.created_at),
                    created_at: t.created_at.clone(),
                }
            })
            .collect();

        // Build filter query string for sort links (excludes sort/order/page).
        let filter_qs = {
            let mut parts = Vec::new();
            if let Some(ref s) = query.status {
                parts.push(format!("status={s}"));
            }
            if let Some(ref p) = query.priority {
                parts.push(format!("priority={p}"));
            }
            if let Some(ref e) = query.effort {
                parts.push(format!("effort={e}"));
            }
            if let Some(ref l) = query.label {
                parts.push(format!("label={l}"));
            }
            if !search_term.is_empty() {
                parts.push(format!("q={search_term}"));
            }
            parts.join("&")
        };

        let proj_name = store.project_name().to_string();
        let sort_ctx = SortContext {
            base_href: format!("/projects/{proj_name}"),
            field: sort_field.as_str().to_string(),
            order: sort_order.as_str().to_string(),
            filter_qs,
        };

        Ok(ProjectTemplate {
            all_projects,
            active_project: Some(name),
            project_name: proj_name,
            stats_open,
            stats_in_progress,
            stats_closed,
            next_up,
            page_tasks,
            all_labels,
            filter_status: query.status,
            filter_priority: query.priority,
            filter_effort: query.effort,
            filter_label: query.label,
            filter_search: search_term,
            page,
            total_pages,
            pagination_pages: (1..=total_pages).collect(),
            sort_ctx,
        })
    })
    .await;

    match result {
        Ok(Ok(tmpl)) => render(tmpl),
        Ok(Err(e)) => error_response(500, &format!("{e}"), &[]),
        Err(e) => error_response(500, &format!("join error: {e}"), &[]),
    }
}

pub(super) async fn task_handler(
    State(state): State<AppState>,
    AxumPath((name, id)): AxumPath<(String, String)>,
) -> Response {
    let root = state.data_root.clone();
    let result = tokio::task::spawn_blocking(move || -> Result<TaskTemplate> {
        let all_projects = list_projects_safe(&root);
        let store = Store::open(&root, &name)?;

        let task_id = db::resolve_task_id(&store, &id, false)?;
        let task = store
            .get_task(&task_id, false)?
            .ok_or_else(|| anyhow::anyhow!("task '{id}' not found"))?;

        // Partition blockers.
        let partition = db::partition_blockers(&store, &task.blockers)?;
        let blockers_open: Vec<BlockerRef> = partition
            .open
            .iter()
            .map(|b| BlockerRef {
                full_id: b.as_str().to_string(),
                short_id: b.short(),
            })
            .collect();
        let blockers_resolved: Vec<BlockerRef> = partition
            .resolved
            .iter()
            .map(|b| BlockerRef {
                full_id: b.as_str().to_string(),
                short_id: b.short(),
            })
            .collect();

        // Find subtasks.
        let all_tasks = store.list_tasks()?;
        let subtasks: Vec<TaskRow> = all_tasks
            .iter()
            .filter(|t| t.parent.as_ref() == Some(&task_id))
            .map(|t| {
                let status = db::status_label(t.status).to_string();
                TaskRow {
                    full_id: t.id.as_str().to_string(),
                    short_id: t.id.short(),
                    status_display: friendly_status(&status),
                    status,
                    priority: db::priority_label(t.priority).to_string(),
                    effort: db::effort_label(t.effort).to_string(),
                    title: t.title.clone(),
                    created_at_display: friendly_date(&t.created_at),
                    created_at: t.created_at.clone(),
                }
            })
            .collect();

        let task_view = TaskView {
            full_id: task.id.as_str().to_string(),
            short_id: task.id.short(),
            title: task.title.clone(),
            description: render_markdown(&task.description),
            task_type: task.task_type.clone(),
            status: db::status_label(task.status).to_string(),
            priority: db::priority_label(task.priority).to_string(),
            effort: db::effort_label(task.effort).to_string(),
            created_at_display: friendly_date(&task.created_at),
            created_at: task.created_at.clone(),
            updated_at_display: friendly_date(&task.updated_at),
            updated_at: task.updated_at.clone(),
            labels: task.labels.clone(),
            logs: task
                .logs
                .iter()
                .map(|l| LogView {
                    timestamp_display: friendly_date(&l.timestamp),
                    timestamp: l.timestamp.clone(),
                    message: render_markdown(&l.message),
                })
                .collect(),
        };

        Ok(TaskTemplate {
            all_projects,
            active_project: Some(name),
            project_name: store.project_name().to_string(),
            task: task_view,
            blockers_open,
            blockers_resolved,
            subtasks,
        })
    })
    .await;

    match result {
        Ok(Ok(tmpl)) => render(tmpl),
        Ok(Err(e)) => error_response(500, &format!("{e}"), &[]),
        Err(e) => error_response(500, &format!("join error: {e}"), &[]),
    }
}
