import json
import os
import uuid
import aiofiles
from datetime import datetime, date
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Form, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_

from app.database import get_db
from app.models.task import Task, TaskComment
from app.models.recurring_task import RecurringTask
from app.models.user import User
from app.models.incoming_email import IncomingEmail
from app.models.notification import Notification
from app.schemas.task import TaskCreate, TaskUpdate, TaskOut, TaskDetailOut, TaskCommentOut
from app.schemas.paged import PagedOut
from app.deps import get_current_user, require_role
from app.services.recurrence import get_next_due_date
from app.services.notifier import notify, notify_user_by_id, notify_user_group, notify_admin_group  # noqa: F401

router = APIRouter(prefix="/tasks", tags=["tasks"])


async def _get_task_or_404(db: AsyncSession, task_id: int) -> Task:
    result = await db.execute(select(Task).where(Task.id == task_id))
    task = result.scalar_one_or_none()
    if not task:
        raise HTTPException(404, "Task not found")
    return task


async def _enrich_task(task: Task, db: AsyncSession) -> dict:
    """Attach username fields and counts to a Task row."""
    assigned_user = None
    if task.assigned_to:
        r = await db.execute(select(User).where(User.id == task.assigned_to))
        assigned_user = r.scalar_one_or_none()

    creator_res = await db.execute(select(User).where(User.id == task.created_by))
    creator = creator_res.scalar_one_or_none()

    subtask_count = await db.scalar(select(func.count()).where(Task.parent_id == task.id))
    comment_count = await db.scalar(select(func.count()).where(TaskComment.task_id == task.id))

    return {
        **task.__dict__,
        "assigned_username":   assigned_user.username if assigned_user else None,
        "created_by_username": creator.username if creator else "",
        "subtask_count":       subtask_count,
        "comment_count":       comment_count,
    }


# ── List (my tasks) ────────────────────────────────────────────────────────────

@router.get("", response_model=PagedOut[TaskOut])
async def list_tasks(
    status: Optional[str] = Query(None),
    parent_id: Optional[int] = Query(None),
    search: Optional[str] = Query(None),
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=500),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    filters = [Task.assigned_to == current_user.id]
    if status:
        filters.append(Task.status == status)
    if parent_id == 0:
        filters.append(Task.parent_id == None)  # noqa: E711
    elif parent_id:
        filters.append(Task.parent_id == parent_id)
    if search:
        filters.append(Task.title.ilike(f"%{search}%"))

    total = await db.scalar(
        select(func.count()).select_from(Task).where(and_(*filters))
    )
    result = await db.execute(
        select(Task)
        .where(and_(*filters))
        .order_by(Task.due_date.asc().nullslast(), Task.created_at.desc())
        .offset((page - 1) * limit).limit(limit)
    )
    items = [await _enrich_task(t, db) for t in result.scalars().all()]
    return PagedOut(items=items, total=total, page=page, limit=limit)


# ── Admin: all tasks ───────────────────────────────────────────────────────────

@router.get("/admin", response_model=PagedOut[TaskOut])
async def list_all_tasks(
    status: Optional[str] = Query(None),
    assigned_to: Optional[int] = Query(None),
    search: Optional[str] = Query(None),
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=500),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(require_role("admin")),
):
    filters = [Task.parent_id == None]  # noqa: E711
    if status:
        filters.append(Task.status == status)
    if assigned_to:
        filters.append(Task.assigned_to == assigned_to)
    if search:
        filters.append(Task.title.ilike(f"%{search}%"))

    total = await db.scalar(
        select(func.count()).select_from(Task).where(and_(*filters))
    )
    result = await db.execute(
        select(Task)
        .where(and_(*filters))
        .order_by(Task.due_date.asc().nullslast(), Task.created_at.desc())
        .offset((page - 1) * limit).limit(limit)
    )
    items = [await _enrich_task(t, db) for t in result.scalars().all()]
    return PagedOut(items=items, total=total, page=page, limit=limit)


# ── Detail ─────────────────────────────────────────────────────────────────────

@router.get("/{task_id}", response_model=TaskDetailOut)
async def get_task(
    task_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    task = await _get_task_or_404(db, task_id)
    enriched = await _enrich_task(task, db)

    sub_result = await db.execute(
        select(Task).where(Task.parent_id == task_id).order_by(Task.created_at)
    )
    subtasks = [await _enrich_task(t, db) for t in sub_result.scalars().all()]

    cmt_result = await db.execute(
        select(TaskComment, User.username)
        .join(User, TaskComment.user_id == User.id)
        .where(TaskComment.task_id == task_id)
        .order_by(TaskComment.created_at)
    )
    comments = [
        TaskCommentOut(
            id=c.id, task_id=c.task_id, user_id=c.user_id,
            username=username, body=c.body, attachment_path=c.attachment_path,
            created_at=c.created_at,
        )
        for c, username in cmt_result.all()
    ]

    return {**enriched, "subtasks": subtasks, "comments": comments}


# ── Create ─────────────────────────────────────────────────────────────────────

@router.post("", response_model=TaskOut, status_code=201)
async def create_task(
    body: TaskCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    task = Task(**body.model_dump(), created_by=current_user.id)
    db.add(task)
    try:
        await db.flush()  # get task.id before notification

        # Notify assigned user (if different from creator)
        if body.assigned_to and body.assigned_to != current_user.id:
            assignee = await db.get(User, body.assigned_to)
            assignee_name = assignee.username if assignee else "-"
            db.add(Notification(
                user_id=body.assigned_to,
                title=f"New task: {body.title}",
                body=f"Assigned by {current_user.username}",
                link=f"/tasks/{task.id}",
            ))
            await notify_user_group(
                db, f"Task baru: {body.title}",
                f"Assignee: {assignee_name} | By: {current_user.username}",
                f"/tasks/{task.id}",
                thread_key="telegram_thread_task",
            )

        await db.commit()
        await db.refresh(task)
    except Exception:
        await db.rollback()
        raise
    return await _enrich_task(task, db)


# ── Update ─────────────────────────────────────────────────────────────────────

@router.patch("/{task_id}", response_model=TaskOut)
async def update_task(
    task_id: int,
    body: TaskUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    task = await _get_task_or_404(db, task_id)
    data = body.model_dump(exclude_unset=True)

    # Non-admin non-creator: only allowed to update status field
    restricted_fields = {k for k in data if k != "status"}
    if restricted_fields and current_user.role != "admin" and task.created_by != current_user.id:
        raise HTTPException(403, "Hanya pembuat task atau admin yang dapat mengubah task ini.")

    new_assignee = data.get("assigned_to")
    reassigned = new_assignee and new_assignee != task.assigned_to and new_assignee != current_user.id

    if data.get("status") == "done" and task.status != "done":
        # Block closing if there are still active subtasks
        active_subtasks = await db.scalar(
            select(func.count()).where(
                Task.parent_id == task_id,
                Task.status != "done",
            )
        )
        if active_subtasks:
            raise HTTPException(
                422,
                f"Tidak bisa menutup task: masih ada {active_subtasks} subtask yang belum selesai."
            )
        task.completed_at = datetime.utcnow()

        # Notify creator (in-app) + admin group (Telegram) that task was completed
        if task.created_by and task.created_by != current_user.id:
            await notify_user_by_id(
                db, task.created_by,
                f"Task selesai: {task.title}",
                f"Diselesaikan oleh {current_user.username}",
                f"/tasks/{task_id}",
            )
        await notify_user_group(db, f"Task selesai: {task.title}", f"Diselesaikan oleh {current_user.username}", f"/tasks/{task_id}", thread_key="telegram_thread_task")

        # If this is a subtask, check if all siblings are now done
        if task.parent_id:
            remaining_siblings = await db.scalar(
                select(func.count()).where(
                    Task.parent_id == task.parent_id,
                    Task.id != task_id,
                    Task.status != "done",
                )
            )
            if remaining_siblings == 0:
                parent = await db.get(Task, task.parent_id)
                if parent and parent.created_by and parent.created_by != current_user.id:
                    await notify_user_by_id(
                        db, parent.created_by,
                        f"Semua subtask selesai: {parent.title}",
                        "Semua subtask sudah done. Task ini siap ditutup.",
                        f"/tasks/{task.parent_id}",
                    )

    elif "status" in data and data["status"] != "done":
        task.completed_at = None

    for k, v in data.items():
        setattr(task, k, v)

    # On-completion trigger: generate next recurring task instance
    if data.get("status") == "done" and task.recurring_task_id:
        rt = await db.get(RecurringTask, task.recurring_task_id)
        if rt and rt.is_active and rt.trigger_mode in ("on_completion", "both"):
            after = task.due_date if task.due_date else date.today()
            if isinstance(after, datetime):
                after = after.date()
            try:
                next_due = get_next_due_date(rt.recurrence_type, rt.recurrence_config, after)
                existing = await db.scalar(
                    select(Task).where(Task.recurring_task_id == rt.id, Task.due_date == next_due)
                )
                if not existing:
                    new_task = Task(
                        title=rt.title,
                        assigned_to=rt.assigned_to,
                        created_by=rt.created_by,
                        due_date=next_due,
                        recurring_task_id=rt.id,
                    )
                    db.add(new_task)
                    await db.flush()
                    rt.last_generated_due = next_due
                    # Notify assignee (in-app) + user group (Telegram)
                    if rt.assigned_to and rt.assigned_to != rt.created_by:
                        await notify_user_by_id(
                            db, rt.assigned_to,
                            f"Task recurring baru: {rt.title}",
                            f"Due date: {next_due}",
                            f"/tasks/{new_task.id}",
                        )
                    await notify_user_group(
                        db,
                        f"Task recurring baru: {rt.title}",
                        f"Due date: {next_due}",
                        f"/tasks/{new_task.id}",
                        thread_key="telegram_thread_task",
                    )
            except Exception as e:
                print(f"[recurring] on_completion generate error: {e}")

    if reassigned:
        reassignee = await db.get(User, new_assignee)
        reassignee_name = reassignee.username if reassignee else "-"
        db.add(Notification(
            user_id=new_assignee,
            title=f"Task assigned to you: {task.title}",
            body=f"Assigned by {current_user.username}",
            link=f"/tasks/{task_id}",
        ))
        await notify_user_group(
            db, f"Task re-assigned: {task.title}",
            f"Assignee: {reassignee_name} | By: {current_user.username}",
            f"/tasks/{task_id}",
            thread_key="telegram_thread_task",
        )

    try:
        await db.commit()
        await db.refresh(task)
    except Exception:
        await db.rollback()
        raise
    return await _enrich_task(task, db)


# ── Delete ─────────────────────────────────────────────────────────────────────

@router.delete("/{task_id}", status_code=204)
async def delete_task(
    task_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    task = await _get_task_or_404(db, task_id)
    if task.created_by != current_user.id and current_user.role != "admin":
        raise HTTPException(403, "Not allowed")
    try:
        await db.delete(task)
        await db.commit()
    except Exception:
        await db.rollback()
        raise


# ── Comments ───────────────────────────────────────────────────────────────────

COMMENT_UPLOAD_DIR = os.path.join(os.path.dirname(__file__), "../../uploads/comments")


async def _save_comment_attachment(file: UploadFile) -> str:
    if not (file.content_type or "").startswith("image/"):
        raise HTTPException(400, "Attachment must be an image")
    os.makedirs(COMMENT_UPLOAD_DIR, exist_ok=True)
    ext = os.path.splitext(file.filename or "")[1] or ".png"
    fname = f"{uuid.uuid4().hex}{ext}"
    fpath = os.path.join(COMMENT_UPLOAD_DIR, fname)
    async with aiofiles.open(fpath, "wb") as f:
        await f.write(await file.read())
    return f"/uploads/comments/{fname}"


@router.post("/{task_id}/comments", response_model=TaskCommentOut, status_code=201)
async def add_comment(
    task_id: int,
    body: str = Form(...),
    file: Optional[UploadFile] = File(None),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    task = await _get_task_or_404(db, task_id)
    attachment_path = await _save_comment_attachment(file) if file else None
    comment = TaskComment(
        task_id=task_id, user_id=current_user.id, body=body,
        attachment_path=attachment_path,
    )
    db.add(comment)

    # Notify assigned user if someone else comments
    notify_ids: set[int] = set()
    if task.assigned_to and task.assigned_to != current_user.id:
        notify_ids.add(task.assigned_to)
    # Also notify task creator if different from commenter and assignee
    if task.created_by and task.created_by != current_user.id:
        notify_ids.add(task.created_by)

    snippet = body[:80] + ("…" if len(body) > 80 else "")
    for uid in notify_ids:
        db.add(Notification(
            user_id=uid,
            title=f"{current_user.username} commented on \"{task.title}\"",
            body=snippet,
            link=f"/tasks/{task_id}",
        ))
    await notify_user_group(db, f"Komentar baru: {task.title}", f"{current_user.username}: {snippet}", f"/tasks/{task_id}", thread_key="telegram_thread_task")

    try:
        await db.commit()
        await db.refresh(comment)
    except Exception:
        await db.rollback()
        raise
    return TaskCommentOut(
        id=comment.id, task_id=comment.task_id, user_id=comment.user_id,
        username=current_user.username, body=comment.body,
        attachment_path=comment.attachment_path, created_at=comment.created_at,
    )


@router.delete("/{task_id}/comments/{comment_id}", status_code=204)
async def delete_comment(
    task_id: int,
    comment_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    result = await db.execute(
        select(TaskComment).where(TaskComment.id == comment_id, TaskComment.task_id == task_id)
    )
    comment = result.scalar_one_or_none()
    if not comment:
        raise HTTPException(404, "Comment not found")
    if comment.user_id != current_user.id and current_user.role != "admin":
        raise HTTPException(403, "Not allowed")
    try:
        await db.delete(comment)
        await db.commit()
    except Exception:
        await db.rollback()
        raise


# ── Letter prefill ─────────────────────────────────────────────────────────────

@router.get("/{task_id}/letter-prefill")
async def letter_prefill(
    task_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    """Return pre-filled subject/body for creating a Letter from a Task."""
    task = await _get_task_or_404(db, task_id)

    inc_result = await db.execute(
        select(IncomingEmail).where(IncomingEmail.task_id == task_id)
    )
    incoming = inc_result.scalar_one_or_none()

    attachments = []
    if incoming and incoming.attachments:
        try:
            attachments = json.loads(incoming.attachments)
        except Exception:
            pass

    return {
        "subject": task.title,
        "body":    incoming.ai_draft if incoming else "",
        "incoming_email": {
            "id":          incoming.id,
            "from_email":  incoming.from_email,
            "subject":     incoming.subject,
            "body_raw":    incoming.body_raw,
            "attachments": attachments,
            "received_at": incoming.received_at.isoformat(),
        } if incoming else None,
    }
