from __future__ import annotations

from typing import List

from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates

from dependencies import (
    get_unit_of_work,
    require_any_role,
    require_project_resource,
    require_roles,
)
from models import MiningOperationType, Project, ScenarioStatus, User
from schemas.project import ProjectCreate, ProjectRead, ProjectUpdate
from services.exceptions import EntityConflictError, EntityNotFoundError
from services.unit_of_work import UnitOfWork

router = APIRouter(prefix="/projects", tags=["Projects"])
templates = Jinja2Templates(directory="templates")

READ_ROLES = ("viewer", "analyst", "project_manager", "admin")
MANAGE_ROLES = ("project_manager", "admin")


def _to_read_model(project: Project) -> ProjectRead:
    return ProjectRead.model_validate(project)


def _require_project_repo(uow: UnitOfWork):
    if not uow.projects:
        raise RuntimeError("Project repository not initialised")
    return uow.projects


def _operation_type_choices() -> list[tuple[str, str]]:
    return [
        (op.value, op.name.replace("_", " ").title()) for op in MiningOperationType
    ]


@router.get("", response_model=List[ProjectRead])
def list_projects(
    _: User = Depends(require_any_role(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
) -> List[ProjectRead]:
    projects = _require_project_repo(uow).list()
    return [_to_read_model(project) for project in projects]


@router.post("", response_model=ProjectRead, status_code=status.HTTP_201_CREATED)
def create_project(
    payload: ProjectCreate,
    _: User = Depends(require_roles(*MANAGE_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
) -> ProjectRead:
    project = Project(**payload.model_dump())
    try:
        created = _require_project_repo(uow).create(project)
    except EntityConflictError as exc:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT, detail=str(exc)
        ) from exc
    return _to_read_model(created)


@router.get(
    "/ui",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="projects.project_list_page",
)
def project_list_page(
    request: Request,
    _: User = Depends(require_any_role(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
) -> HTMLResponse:
    projects = _require_project_repo(uow).list(with_children=True)
    for project in projects:
        setattr(project, "scenario_count", len(project.scenarios))
    return templates.TemplateResponse(
        request,
        "projects/list.html",
        {
            "projects": projects,
        },
    )


@router.get(
    "/create",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="projects.create_project_form",
)
def create_project_form(
    request: Request, _: User = Depends(require_roles(*MANAGE_ROLES))
) -> HTMLResponse:
    return templates.TemplateResponse(
        request,
        "projects/form.html",
        {
            "project": None,
            "operation_types": _operation_type_choices(),
            "form_action": request.url_for("projects.create_project_submit"),
            "cancel_url": request.url_for("projects.project_list_page"),
        },
    )


@router.post(
    "/create",
    include_in_schema=False,
    name="projects.create_project_submit",
)
def create_project_submit(
    request: Request,
    _: User = Depends(require_roles(*MANAGE_ROLES)),
    name: str = Form(...),
    location: str | None = Form(None),
    operation_type: str = Form(...),
    description: str | None = Form(None),
    uow: UnitOfWork = Depends(get_unit_of_work),
):
    def _normalise(value: str | None) -> str | None:
        if value is None:
            return None
        value = value.strip()
        return value or None

    try:
        op_type = MiningOperationType(operation_type)
    except ValueError as exc:
        return templates.TemplateResponse(
            request,
            "projects/form.html",
            {
                "project": None,
                "operation_types": _operation_type_choices(),
                "form_action": request.url_for("projects.create_project_submit"),
                "cancel_url": request.url_for("projects.project_list_page"),
                "error": "Invalid operation type.",
            },
            status_code=status.HTTP_400_BAD_REQUEST,
        )

    project = Project(
        name=name.strip(),
        location=_normalise(location),
        operation_type=op_type,
        description=_normalise(description),
    )
    try:
        _require_project_repo(uow).create(project)
    except EntityConflictError as exc:
        return templates.TemplateResponse(
            request,
            "projects/form.html",
            {
                "project": project,
                "operation_types": _operation_type_choices(),
                "form_action": request.url_for("projects.create_project_submit"),
                "cancel_url": request.url_for("projects.project_list_page"),
                "error": "Project with this name already exists.",
            },
            status_code=status.HTTP_409_CONFLICT,
        )

    return RedirectResponse(
        request.url_for("projects.project_list_page"),
        status_code=status.HTTP_303_SEE_OTHER,
    )


@router.get("/{project_id}", response_model=ProjectRead)
def get_project(project: Project = Depends(require_project_resource())) -> ProjectRead:
    return _to_read_model(project)


@router.put("/{project_id}", response_model=ProjectRead)
def update_project(
    payload: ProjectUpdate,
    project: Project = Depends(
        require_project_resource(require_manage=True)
    ),
    uow: UnitOfWork = Depends(get_unit_of_work),
) -> ProjectRead:
    update_data = payload.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(project, field, value)

    uow.flush()
    return _to_read_model(project)


@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_project(
    project: Project = Depends(require_project_resource(require_manage=True)),
    uow: UnitOfWork = Depends(get_unit_of_work),
) -> None:
    _require_project_repo(uow).delete(project.id)


@router.get(
    "/{project_id}/view",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="projects.view_project",
)
def view_project(
    request: Request,
    project: Project = Depends(require_project_resource()),
    uow: UnitOfWork = Depends(get_unit_of_work),
) -> HTMLResponse:
    project = _require_project_repo(uow).get(project.id, with_children=True)

    scenarios = sorted(project.scenarios, key=lambda s: s.created_at)
    scenario_stats = {
        "total": len(scenarios),
        "active": sum(1 for scenario in scenarios if scenario.status == ScenarioStatus.ACTIVE),
        "draft": sum(1 for scenario in scenarios if scenario.status == ScenarioStatus.DRAFT),
        "archived": sum(1 for scenario in scenarios if scenario.status == ScenarioStatus.ARCHIVED),
        "latest_update": max(
            (scenario.updated_at for scenario in scenarios if scenario.updated_at),
            default=None,
        ),
    }
    return templates.TemplateResponse(
        request,
        "projects/detail.html",
        {
            "project": project,
            "scenarios": scenarios,
            "scenario_stats": scenario_stats,
        },
    )


@router.get(
    "/{project_id}/edit",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="projects.edit_project_form",
)
def edit_project_form(
    request: Request,
    project: Project = Depends(
        require_project_resource(require_manage=True)
    ),
) -> HTMLResponse:
    return templates.TemplateResponse(
        request,
        "projects/form.html",
        {
            "project": project,
            "operation_types": _operation_type_choices(),
            "form_action": request.url_for(
                "projects.edit_project_submit", project_id=project.id
            ),
            "cancel_url": request.url_for(
                "projects.view_project", project_id=project.id
            ),
        },
    )


@router.post(
    "/{project_id}/edit",
    include_in_schema=False,
    name="projects.edit_project_submit",
)
def edit_project_submit(
    request: Request,
    project: Project = Depends(
        require_project_resource(require_manage=True)
    ),
    name: str = Form(...),
    location: str | None = Form(None),
    operation_type: str | None = Form(None),
    description: str | None = Form(None),
    uow: UnitOfWork = Depends(get_unit_of_work),
):
    def _normalise(value: str | None) -> str | None:
        if value is None:
            return None
        value = value.strip()
        return value or None

    project.name = name.strip()
    project.location = _normalise(location)
    if operation_type:
        try:
            project.operation_type = MiningOperationType(operation_type)
        except ValueError as exc:
            return templates.TemplateResponse(
                request,
                "projects/form.html",
                {
                    "project": project,
                    "operation_types": _operation_type_choices(),
                    "form_action": request.url_for(
                        "projects.edit_project_submit", project_id=project.id
                    ),
                    "cancel_url": request.url_for(
                        "projects.view_project", project_id=project.id
                    ),
                    "error": "Invalid operation type.",
                },
                status_code=status.HTTP_400_BAD_REQUEST,
            )
    project.description = _normalise(description)

    uow.flush()

    return RedirectResponse(
        request.url_for("projects.view_project", project_id=project.id),
        status_code=status.HTTP_303_SEE_OTHER,
    )
