"""Service layer for dashboard page data."""

from __future__ import annotations

import html
from datetime import date, datetime, timedelta
from pathlib import Path
from typing import Any

from sqlalchemy.orm import Session

from app.models.engagement import Engagement
from app.models.event import Event
from app.schemas.coverage import CoverageSummaryRead
from app.schemas.timeline import TimelineEventRead, TimelineGroupedRead
from app.services import (
    ai_analysis_service,
    coverage_engine,
    engagements as engagement_service,
    events as event_service,
    report_generator,
    timeline as timeline_service,
)
from app.services.integrations import service as integrations_service
from app.services.integrations.registry import PROVIDER_META

try:
    import markdown as markdown_lib
except ModuleNotFoundError:  # pragma: no cover - optional dependency at runtime
    markdown_lib = None


def _project_root() -> Path:
    """Return repository root."""
    return Path(__file__).resolve().parents[2]


def _render_markdown(markdown_text: str) -> str:
    """Render markdown to HTML with safe fallback when dependency is missing."""
    if markdown_lib is None:
        return f"<pre>{html.escape(markdown_text)}</pre>"
    return markdown_lib.markdown(
        markdown_text,
        extensions=["extra", "sane_lists", "tables"],
        output_format="html5",
    )


def _require_engagement(db: Session, engagement_id: int) -> Engagement:
    """Return engagement or raise lookup error."""
    engagement = engagement_service.get_engagement_by_id(db, engagement_id)
    if engagement is None:
        raise LookupError("engagement not found")
    return engagement


def get_home_context(db: Session, selected_engagement_id: int | None = None) -> dict[str, Any]:
    """Build dashboard home context."""
    engagements = engagement_service.list_engagements(db)
    selected: Engagement | None = None
    if selected_engagement_id is not None:
        selected = engagement_service.get_engagement_by_id(db, selected_engagement_id)
        if selected is None:
            raise LookupError("engagement not found")
    elif engagements:
        selected = engagements[0]

    coverage: CoverageSummaryRead | None = None
    latest_events: list[Event] = []
    if selected is not None:
        coverage = coverage_engine.get_coverage(db=db, engagement_id=selected.id)
        latest_events = event_service.list_events(db=db, engagement_id=selected.id)[:10]

    return {
        "engagements": engagements,
        "selected_engagement": selected,
        "coverage": coverage,
        "latest_events": latest_events,
    }


def get_engagement_context(
    db: Session,
    engagement_id: int,
    run_ai: bool = False,
    timeline_mode: str = "chronological",
) -> dict[str, Any]:
    """Build engagement detail context for dashboard."""
    engagement = _require_engagement(db, engagement_id)
    coverage = coverage_engine.get_coverage(db=db, engagement_id=engagement_id)
    use_grouped = timeline_mode == "grouped"
    timeline_data = timeline_service.get_timeline(db=db, engagement_id=engagement_id, group_by_phase=use_grouped)
    timeline_events: list[TimelineEventRead] = []
    grouped_timeline: TimelineGroupedRead | None = None
    if isinstance(timeline_data, list):
        timeline_events = timeline_data
    else:
        grouped_timeline = timeline_data

    ai_analysis: dict[str, Any] | None = None
    ai_error: str | None = None
    if run_ai:
        try:
            ai_analysis = ai_analysis_service.analyze_engagement(db=db, engagement_id=engagement_id)
        except Exception as exc:
            ai_error = str(exc)

    return {
        "engagement": engagement,
        "coverage": coverage,
        "timeline_events": timeline_events,
        "grouped_timeline": grouped_timeline,
        "ai_analysis": ai_analysis,
        "ai_error": ai_error,
        "run_ai": run_ai,
        "timeline_mode": timeline_mode,
    }


def get_report_context(
    db: Session,
    engagement_id: int,
    *,
    include_vault: bool = False,
    include_ai: bool | None = None,
    vault_query: str | None = None,
) -> dict[str, Any]:
    """Generate report payload and convert markdown to HTML."""
    engagement = _require_engagement(db, engagement_id)
    payload = report_generator.generate_report_payload(
        db=db,
        engagement_id=engagement_id,
        include_vault=include_vault,
        include_ai=include_ai,
        vault_query=vault_query,
    )
    report_markdown = payload["report_markdown"]
    report_html = _render_markdown(report_markdown)
    return {
        "engagement": engagement,
        "engagement_id": payload["engagement_id"],
        "generated_at": payload["generated_at"],
        "report_markdown": report_markdown,
        "report_html": report_html,
        "include_vault": include_vault,
        "include_ai": include_ai,
        "vault_query": vault_query or "",
    }


def get_notes_context(db: Session, engagement_id: int) -> dict[str, Any]:
    """Build notes preview context for one engagement."""
    engagement = _require_engagement(db, engagement_id)
    note_path = _project_root() / "notes" / f"engagement_{engagement_id}.md"
    notes_exists = note_path.exists() and note_path.is_file()

    notes_markdown = ""
    notes_html = "<p class='muted'>No notes generated yet for this engagement.</p>"
    updated_at = None

    if notes_exists:
        notes_markdown = note_path.read_text(encoding="utf-8")
        notes_html = _render_markdown(notes_markdown)
        updated_at = note_path.stat().st_mtime

    return {
        "engagement": engagement,
        "engagement_id": engagement.id,
        "notes_exists": notes_exists,
        "notes_path": (Path("notes") / note_path.name).as_posix(),
        "notes_markdown": notes_markdown,
        "notes_html": notes_html,
        "notes_updated_ts": updated_at,
    }


TASK_VIEW_OPTIONS = ("today", "week", "month", "all")


def _normalize_task_view(raw_view: str | None) -> str:
    value = str(raw_view or "").strip().lower()
    if value in TASK_VIEW_OPTIONS:
        return value
    return "all"


def _coerce_date(raw_value: object | None) -> date | None:
    if raw_value is None:
        return None
    text = str(raw_value).strip()
    if not text:
        return None
    candidate = text[:10]
    try:
        return datetime.strptime(candidate, "%Y-%m-%d").date()
    except ValueError:
        return None


def _task_due_date(task: dict[str, Any]) -> date | None:
    due_payload = task.get("due")
    if isinstance(due_payload, dict):
        parsed = _coerce_date(due_payload.get("date") or due_payload.get("string"))
        if parsed is not None:
            return parsed
    parsed = _coerce_date(task.get("due_date") or task.get("due_string"))
    return parsed


def _provider_label(provider_id: str) -> str:
    meta = PROVIDER_META.get(provider_id)
    if meta is None:
        return provider_id or "unknown"
    return meta.label


def _task_in_view(
    *,
    due_date: date | None,
    task_view: str,
    today: date,
    week_start: date,
    week_end: date,
    month_start: date,
    month_end: date,
    completed: bool,
) -> bool:
    if task_view == "all":
        return True
    if due_date is None:
        return False
    if task_view == "today":
        return due_date == today or (not completed and due_date < today)
    if task_view == "week":
        return week_start <= due_date <= week_end
    if task_view == "month":
        return month_start <= due_date <= month_end
    return True


def _task_sort_key(task: dict[str, Any], *, today: date) -> tuple[int, date, int, str]:
    due_date = task.get("due_date_obj")
    completed = bool(task.get("completed"))
    overdue = bool(task.get("overdue"))
    priority = task.get("priority")
    if isinstance(priority, int):
        priority_sort = -priority
    else:
        priority_sort = 0

    if completed:
        group = 4
    elif overdue:
        group = 0
    elif due_date == today:
        group = 1
    elif isinstance(due_date, date):
        group = 2
    else:
        group = 3

    sort_date = due_date if isinstance(due_date, date) else date.max
    return (group, sort_date, priority_sort, str(task.get("title") or "").lower())


def get_tasks_context(*, task_view: str = "all", show_completed: bool = False) -> dict[str, Any]:
    """Build tasks dashboard context with view filters and urgency sorting."""
    tasks: list[dict[str, Any]] = []
    error_message: str | None = None
    normalized_view = _normalize_task_view(task_view)
    include_completed = bool(show_completed)

    today = datetime.now().astimezone().date()
    week_start = today - timedelta(days=today.weekday())
    week_end = week_start + timedelta(days=6)
    month_start = today.replace(day=1)
    if month_start.month == 12:
        next_month = month_start.replace(year=month_start.year + 1, month=1, day=1)
    else:
        next_month = month_start.replace(month=month_start.month + 1, day=1)
    month_end = next_month - timedelta(days=1)

    try:
        raw_tasks = integrations_service.list_tasks(across_all=True, include_completed=True)
    except Exception as exc:
        error_message = f"Unexpected tasks error: {exc}"
        raw_tasks = []

    for item in raw_tasks:
        provider_id = str(item.get("provider") or "").strip() or "unknown"
        due_date = _task_due_date(item)
        completed = bool(item.get("completed"))
        overdue = bool(not completed and due_date is not None and due_date < today)
        title = str(item.get("title") or item.get("content") or "").strip() or "Untitled task"
        priority_raw = item.get("priority")
        priority_value = priority_raw if isinstance(priority_raw, int) else None
        if not _task_in_view(
            due_date=due_date,
            task_view=normalized_view,
            today=today,
            week_start=week_start,
            week_end=week_end,
            month_start=month_start,
            month_end=month_end,
            completed=completed,
        ):
            continue
        if not include_completed and completed:
            continue

        tasks.append(
            {
                "id": str(item.get("id", "")),
                "title": title,
                "provider": provider_id,
                "provider_label": _provider_label(provider_id),
                "description": str(item.get("description", "")).strip() or None,
                "due_date": due_date.isoformat() if due_date else None,
                "due_date_obj": due_date,
                "priority": priority_value,
                "completed": completed,
                "overdue": overdue,
            },
        )

    tasks.sort(key=lambda item: _task_sort_key(item, today=today))

    overdue_count = sum(1 for item in tasks if item["overdue"] and not item["completed"])
    today_count = sum(1 for item in tasks if item["due_date_obj"] == today and not item["completed"])
    completed_count = sum(1 for item in tasks if item["completed"])

    return {
        "tasks": tasks,
        "error_message": error_message,
        "task_view": normalized_view,
        "show_completed": include_completed,
        "view_options": TASK_VIEW_OPTIONS,
        "today_iso": today.isoformat(),
        "overdue_count": overdue_count,
        "today_count": today_count,
        "completed_count": completed_count,
    }
