"""Projects services — business logic.

All write operations go through these functions; views must NOT call ORM
save/delete directly on Projects models.
"""

from __future__ import annotations

from datetime import date
from decimal import Decimal

from django.db import transaction
from django.utils import timezone


# ---------------------------------------------------------------------------
# ProjectTag services
# ---------------------------------------------------------------------------

def create_project_tag(*, tenant_id: int, name: str, color: str = "") -> "ProjectTag":
    from simorgh.apps.projects.models import ProjectTag
    return ProjectTag.objects.create(tenant_id=tenant_id, name=name.strip(), color=color)


def update_project_tag(tag: "ProjectTag", *, name: str | None = None, color: str | None = None) -> "ProjectTag":
    if name is not None:
        tag.name = name.strip()
    if color is not None:
        tag.color = color
    tag.save()
    return tag


def delete_project_tag(tag: "ProjectTag") -> None:
    tag.delete()


# ---------------------------------------------------------------------------
# Project services
# ---------------------------------------------------------------------------

def create_project(
    *,
    tenant_id: int,
    organization_node_id: int,
    name: str,
    owner_id: int,
    code: str = "",
    description: str = "",
    status: str = "draft",
    start_date: date | None = None,
    end_date: date | None = None,
    color: str = "#4f46e5",
    is_billable: bool = False,
    budget: Decimal | None = None,
    contact_id: str | None = None,
    tag_ids: list[int] | None = None,
) -> "Project":
    from simorgh.apps.projects.models import Project, ProjectTag
    from simorgh.apps.events.bus import dispatch

    if code and Project.objects.filter(tenant_id=tenant_id, code=code).exists():
        raise ValueError("This project code already exists.")
    if end_date and start_date and end_date < start_date:
        raise ValueError("end_date cannot be earlier than start_date.")

    with transaction.atomic():
        project = Project.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name.strip(),
            code=code.strip(),
            description=description,
            status=status,
            start_date=start_date,
            end_date=end_date,
            owner_id=owner_id,
            color=color,
            is_billable=is_billable,
            budget=budget,
            contact_id=contact_id,
        )
        if tag_ids:
            tags = ProjectTag.objects.filter(tenant_id=tenant_id, pk__in=tag_ids)
            project.tags.set(tags)

    dispatch("projects.project.created", {
        "project_id": str(project.public_id),
        "tenant_id": tenant_id,
        "owner_id": owner_id,
    })
    return project


def update_project(
    project: "Project",
    *,
    name: str | None = None,
    code: str | None = None,
    description: str | None = None,
    status: str | None = None,
    start_date: "date | None" = ...,  # type: ignore[assignment]
    end_date: "date | None" = ...,  # type: ignore[assignment]
    color: str | None = None,
    is_billable: bool | None = None,
    budget: "Decimal | None" = ...,  # type: ignore[assignment]
    contact_id: "str | None" = ...,  # type: ignore[assignment]
    tag_ids: list[int] | None = None,
    actor_id: int | None = None,
) -> "Project":
    _SENTINEL = ...  # type: ignore[assignment]

    from simorgh.apps.events.bus import dispatch

    old_status = project.status

    if name is not None:
        project.name = name.strip()
    if code is not None:
        new_code = code.strip()
        if new_code and new_code != project.code:
            from simorgh.apps.projects.models import Project as _P
            if _P.objects.filter(tenant_id=project.tenant_id, code=new_code).exclude(pk=project.pk).exists():
                raise ValueError("This project code already exists.")
        project.code = new_code
    if description is not None:
        project.description = description
    if status is not None:
        project.status = status
    if start_date is not _SENTINEL:
        project.start_date = start_date  # type: ignore[assignment]
    if end_date is not _SENTINEL:
        project.end_date = end_date  # type: ignore[assignment]
    if color is not None:
        project.color = color
    if is_billable is not None:
        project.is_billable = is_billable
    if budget is not _SENTINEL:
        project.budget = budget  # type: ignore[assignment]
    if contact_id is not _SENTINEL:
        project.contact_id = contact_id  # type: ignore[assignment]

    # Validate dates
    sd = project.start_date
    ed = project.end_date
    if sd and ed and ed < sd:
        raise ValueError("end_date cannot be earlier than start_date.")

    project.save()

    if tag_ids is not None:
        from simorgh.apps.projects.models import ProjectTag
        tags = ProjectTag.objects.filter(tenant_id=project.tenant_id, pk__in=tag_ids)
        project.tags.set(tags)

    if status is not None and status != old_status:
        dispatch("projects.project.status_changed", {
            "project_id": str(project.public_id),
            "from_status": old_status,
            "to_status": status,
            "tenant_id": project.tenant_id,
            "actor_id": actor_id,
        })
        if status == "completed":
            dispatch("projects.project.completed", {
                "project_id": str(project.public_id),
                "tenant_id": project.tenant_id,
                "actor_id": actor_id,
            })

    return project


def delete_project(project: "Project") -> None:
    project.delete()


def change_project_status(project: "Project", *, new_status: str, actor_id: int | None = None) -> "Project":
    return update_project(project, status=new_status, actor_id=actor_id)


# ---------------------------------------------------------------------------
# ProjectMember services
# ---------------------------------------------------------------------------

def add_project_member(
    project: "Project",
    *,
    user_id: int,
    role: str = "member",
    actor_id: int | None = None,
) -> "ProjectMember":
    from simorgh.apps.projects.models import ProjectMember
    from simorgh.apps.events.bus import dispatch

    member, created = ProjectMember.objects.get_or_create(
        project=project,
        user_id=user_id,
        defaults={"role": role},
    )
    if not created:
        member.role = role
        member.save()

    if created:
        dispatch("projects.project.member_added", {
            "project_id": str(project.public_id),
            "user_id": user_id,
            "role": role,
            "tenant_id": project.tenant_id,
            "actor_id": actor_id,
        })
    return member


def remove_project_member(
    project: "Project",
    *,
    user_id: int,
    actor_id: int | None = None,
) -> None:
    from simorgh.apps.projects.models import ProjectMember
    from simorgh.apps.events.bus import dispatch

    ProjectMember.objects.filter(project=project, user_id=user_id).delete()
    dispatch("projects.project.member_removed", {
        "project_id": str(project.public_id),
        "user_id": user_id,
        "tenant_id": project.tenant_id,
        "actor_id": actor_id,
    })


# ---------------------------------------------------------------------------
# ProjectPhase services
# ---------------------------------------------------------------------------

def create_phase(
    project: "Project",
    *,
    name: str,
    order: int = 0,
    start_date: date | None = None,
    end_date: date | None = None,
    status: str = "planned",
    description: str = "",
) -> "ProjectPhase":
    from simorgh.apps.projects.models import ProjectPhase
    return ProjectPhase.objects.create(
        project=project,
        name=name.strip(),
        order=order,
        start_date=start_date,
        end_date=end_date,
        status=status,
        description=description,
    )


def update_phase(
    phase: "ProjectPhase",
    *,
    name: str | None = None,
    order: int | None = None,
    start_date: date | None = ...,  # type: ignore[assignment]
    end_date: date | None = ...,  # type: ignore[assignment]
    status: str | None = None,
    description: str | None = None,
) -> "ProjectPhase":
    _SENTINEL = ...  # type: ignore[assignment]
    if name is not None:
        phase.name = name.strip()
    if order is not None:
        phase.order = order
    if start_date is not _SENTINEL:
        phase.start_date = start_date  # type: ignore[assignment]
    if end_date is not _SENTINEL:
        phase.end_date = end_date  # type: ignore[assignment]
    if status is not None:
        phase.status = status
    if description is not None:
        phase.description = description
    phase.save()
    return phase


def delete_phase(phase: "ProjectPhase") -> None:
    phase.delete()


# ---------------------------------------------------------------------------
# Milestone services
# ---------------------------------------------------------------------------

def create_milestone(
    project: "Project",
    *,
    name: str,
    due_date: date,
    description: str = "",
) -> "Milestone":
    from simorgh.apps.projects.models import Milestone
    return Milestone.objects.create(
        project=project,
        name=name.strip(),
        due_date=due_date,
        description=description,
    )


def update_milestone(
    milestone: "Milestone",
    *,
    name: str | None = None,
    due_date: date | None = None,
    description: str | None = None,
) -> "Milestone":
    if name is not None:
        milestone.name = name.strip()
    if due_date is not None:
        milestone.due_date = due_date
    if description is not None:
        milestone.description = description
    milestone.save()
    return milestone


def complete_milestone(
    milestone: "Milestone",
    *,
    actor_id: int | None = None,
) -> "Milestone":
    from simorgh.apps.events.bus import dispatch

    if milestone.is_completed:
        return milestone

    milestone.is_completed = True
    milestone.completed_at = timezone.now()
    milestone.save()

    dispatch("projects.milestone.completed", {
        "milestone_id": str(milestone.public_id),
        "project_id": str(milestone.project.public_id),
        "tenant_id": milestone.project.tenant_id,
        "actor_id": actor_id,
    })
    return milestone


def delete_milestone(milestone: "Milestone") -> None:
    milestone.delete()
