import pytest
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from apps.tasks.models import Task, Message, Approval
from apps.companies.models import Organization, OrganizationMembership


@pytest.fixture
def organization(db):
    return Organization.objects.get_or_create(name="Task Test Org")[0]


@pytest.fixture
def user(db, organization):
    User = get_user_model()
    u = User.objects.create_user(email="task@example.com", password="pass123")
    OrganizationMembership.objects.get_or_create(organization=organization, user=u)
    return u


@pytest.fixture
def api_user(user, organization):
    """User with the tasks RBAC permission (endpoints are HasMenuPermission-gated)
    and an Organization membership (needed to resolve the caller's active org)."""
    from apps.core.models import Permission, Role, UserRole
    role = Role.objects.create(name="Task Tester", slug="task-tester")
    perm, _ = Permission.objects.get_or_create(
        domain="tasks", action="*", defaults={"label": "All tasks"})
    role.permissions.add(perm)
    UserRole.objects.create(user=user, role=role, organization=organization)
    return user


@pytest.fixture
def task(user, organization):
    return Task.objects.create(
        organization=organization,
        title="Fix bug",
        description="Fix login bug",
        created_by=user
    )


def _grant_tasks_permission(target_user):
    """Give an arbitrary user the tasks RBAC permission — messages/approvals
    endpoints are HasMenuPermission-gated same as the task endpoints. Also
    joins an Organization: posting a Message with related_task writes an
    AuditLog, which requires the caller to resolve an active org."""
    from apps.core.models import Permission, Role, UserRole
    from apps.companies.models import Organization, OrganizationMembership
    org, _ = Organization.objects.get_or_create(name="Task Test Org")
    OrganizationMembership.objects.get_or_create(organization=org, user=target_user)
    role = Role.objects.create(name=f"Task Tester {target_user.pk}", slug=f"task-tester-{target_user.pk}")
    perm, _ = Permission.objects.get_or_create(
        domain="tasks", action="*", defaults={"label": "All tasks"})
    role.permissions.add(perm)
    UserRole.objects.create(user=target_user, role=role, organization=org)
    return target_user


def _response_rows(response):
    data = response.data
    return data["results"] if isinstance(data, dict) and "results" in data else data


@pytest.mark.django_db
class TestTaskModel:
    def test_create_task(self, user, organization):
        t = Task.objects.create(
            organization=organization,
            title="New Task",
            description="Task description",
            created_by=user
        )
        assert t.title == "New Task"
        assert t.status == "todo"
        assert t.priority == "medium"

    def test_task_statuses(self, user, organization):
        for status in ["todo", "in_progress", "review", "done", "cancelled"]:
            t = Task.objects.create(organization=organization, title=f"Task {status}", created_by=user, status=status)
            assert t.status == status


@pytest.mark.django_db
class TestMessageModel:
    def test_create_message(self, user, organization):
        msg = Message.objects.create(
            organization=organization,
            sender=user,
            content="Hello there"
        )
        assert msg.content == "Hello there"
        assert msg.is_read is False


@pytest.mark.django_db
class TestApprovalModel:
    def test_create_approval(self, user, organization):
        approver = user
        approval = Approval.objects.create(
            organization=organization,
            title="Leave Request",
            description="Please approve",
            requester=user,
            approver=approver
        )
        assert approval.status == "pending"


@pytest.mark.django_db
class TestTaskAPI:
    def test_post_task_sets_created_by(self, api_user):
        user = api_user
        client = APIClient()
        client.force_authenticate(user=user)
        res = client.post("/api/tasks/", {"title": "API task"}, format="json")
        assert res.status_code == 201, res.content
        assert res.data["title"] == "API task"
        assert res.data["status"] == "todo"
        assert res.data["priority"] == "medium"
        task = Task.objects.get(pk=res.data["id"])
        assert task.created_by_id == user.id

    def test_post_task_with_optional_fields(self, api_user):
        client = APIClient()
        client.force_authenticate(user=api_user)
        res = client.post(
            "/api/tasks/",
            {"title": "Detailed", "description": "desc", "priority": "high", "status": "in_progress"},
            format="json",
        )
        assert res.status_code == 201, res.content
        assert res.data["priority"] == "high"
        assert res.data["status"] == "in_progress"

    def test_post_task_requires_title(self, api_user):
        client = APIClient()
        client.force_authenticate(user=api_user)
        res = client.post("/api/tasks/", {}, format="json")
        assert res.status_code == 400


@pytest.mark.django_db
class TestApprovalAPI:
    def test_post_approval_sets_requester(self, api_user):
        user = api_user
        User = get_user_model()
        approver = User.objects.create_user(email="approver@example.com", password="pass123")
        client = APIClient()
        client.force_authenticate(user=user)
        res = client.post(
            "/api/approvals/",
            {"title": "Need approval", "description": "why", "approver": str(approver.id)},
            format="json",
        )
        assert res.status_code == 201, res.content
        assert str(res.data["requester"]) == str(user.id)
        assert str(res.data["approver"]) == str(approver.id)
        assert res.data["status"] == "pending"

    def test_post_approval_requires_approver(self, api_user):
        client = APIClient()
        client.force_authenticate(user=api_user)
        res = client.post(
            "/api/approvals/",
            {"title": "Need approval", "description": "why"},
            format="json",
        )
        assert res.status_code == 400


@pytest.mark.django_db
class TestTaskCommentNotify:
    def test_comment_notifies_assignee_and_creator_not_commenter(self, api_user):
        User = get_user_model()
        creator = api_user
        assignee = User.objects.create_user(email="assignee@example.com", password="pass123")
        commenter = _grant_tasks_permission(User.objects.create_user(email="commenter@example.com", password="pass123"))
        task = Task.objects.create(organization=creator.organizations.first(), title="Comment target", created_by=creator, assignee=assignee)

        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=commenter)
        res = client.post("/api/messages/", {"content": "Looks good", "related_task": str(task.id)}, format="json")
        assert res.status_code == 201, res.content

        assert Notification.objects.filter(user=creator, notification_type="task", message__icontains="commented").exists()
        assert Notification.objects.filter(user=assignee, notification_type="task", message__icontains="commented").exists()
        assert not Notification.objects.filter(user=commenter, notification_type="task", message__icontains="commented").exists()

    def test_no_notify_when_commenter_is_assignee(self, api_user):
        User = get_user_model()
        creator = api_user
        assignee = _grant_tasks_permission(User.objects.create_user(email="assignee2@example.com", password="pass123"))
        task = Task.objects.create(organization=creator.organizations.first(), title="Self comment", created_by=creator, assignee=assignee)

        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=assignee)
        client.post("/api/messages/", {"content": "On it", "related_task": str(task.id)}, format="json")
        assert not Notification.objects.filter(user=assignee, notification_type="task", message__icontains="commented").exists()
        assert Notification.objects.filter(user=creator, notification_type="task", message__icontains="commented").exists()


@pytest.mark.django_db
class TestTaskMentionNotify:
    def test_mention_by_full_name_notifies_matched_user(self, api_user):
        User = get_user_model()
        creator = api_user
        mentioned = User.objects.create_user(email="jane@example.com", password="pass123", first_name="Jane", last_name="Doe")
        commenter = _grant_tasks_permission(User.objects.create_user(email="commenter2@example.com", password="pass123"))
        task = Task.objects.create(organization=creator.organizations.first(), title="Mention target", created_by=creator)

        from apps.projects.models import Project, ProjectTeamMember
        project = Project.objects.create(organization=commenter.organizations.first(), name="Mention Project")
        ProjectTeamMember.objects.create(project=project, user=mentioned, role="member")
        task.project = project
        task.save(update_fields=["project"])

        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=commenter)
        res = client.post(
            "/api/messages/",
            {"content": "@Jane Doe can you check this?", "related_task": str(task.id)},
            format="json",
        )
        assert res.status_code == 201, res.content
        assert Notification.objects.filter(user=mentioned, notification_type="task", title="You were mentioned").exists()

    def test_mention_of_user_without_task_access_is_skipped(self, api_user):
        User = get_user_model()
        creator = api_user
        outsider = User.objects.create_user(email="out@example.com", password="pass123", first_name="Out", last_name="Sider")
        commenter = _grant_tasks_permission(User.objects.create_user(email="commenter3@example.com", password="pass123"))
        task = Task.objects.create(organization=creator.organizations.first(), title="No access mention", created_by=creator)

        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=commenter)
        res = client.post(
            "/api/messages/",
            {"content": "@Out Sider please review", "related_task": str(task.id)},
            format="json",
        )
        assert res.status_code == 201, res.content
        assert not Notification.objects.filter(user=outsider, notification_type="task", title="You were mentioned").exists()

    def test_ambiguous_name_mention_is_skipped(self, api_user):
        User = get_user_model()
        creator = api_user
        User.objects.create_user(email="dup1@example.com", password="pass123", first_name="Sam", last_name="Lee")
        dup2 = User.objects.create_user(email="dup2@example.com", password="pass123", first_name="Sam", last_name="Lee")
        commenter = _grant_tasks_permission(User.objects.create_user(email="commenter4@example.com", password="pass123"))
        task = Task.objects.create(organization=creator.organizations.first(), title="Ambiguous mention", created_by=creator, assignee=dup2)

        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=commenter)
        res = client.post(
            "/api/messages/",
            {"content": "@Sam Lee thoughts?", "related_task": str(task.id)},
            format="json",
        )
        assert res.status_code == 201, res.content
        # dup2 is the assignee so gets the plain comment notify, but never the
        # separate "You were mentioned" one — the name match is ambiguous.
        assert not Notification.objects.filter(user=dup2, notification_type="task", title="You were mentioned").exists()


@pytest.mark.django_db
class TestApprovalDecideNotify:
    def test_decide_notifies_requester(self, api_user):
        User = get_user_model()
        requester = api_user
        approver = _grant_tasks_permission(User.objects.create_user(email="decide-approver@example.com", password="pass123"))
        approval = Approval.objects.create(organization=requester.organizations.first(), title="Need sign-off", description="why", requester=requester, approver=approver)

        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=approver)
        res = client.post(f"/api/approvals/{approval.id}/decide/", {"status": "approved"}, format="json")
        assert res.status_code == 200, res.content
        assert Notification.objects.filter(user=requester, notification_type="task", title="Approval approved").exists()

    def test_no_self_notify_when_requester_is_approver(self, user):
        _grant_tasks_permission(user)
        approval = Approval.objects.create(organization=user.organizations.first(), title="Self sign-off", description="why", requester=user, approver=user)
        from apps.notifications.models import Notification
        client = APIClient()
        client.force_authenticate(user=user)
        res = client.post(f"/api/approvals/{approval.id}/decide/", {"status": "approved"}, format="json")
        assert res.status_code == 200, res.content
        assert not Notification.objects.filter(user=user, notification_type="task", title="Approval approved").exists()

    def test_decide_denied_for_non_approver(self, api_user):
        User = get_user_model()
        requester = api_user
        approver = _grant_tasks_permission(User.objects.create_user(email="decide-approver2@example.com", password="pass123"))
        outsider = _grant_tasks_permission(User.objects.create_user(email="decide-outsider@example.com", password="pass123"))
        approval = Approval.objects.create(organization=requester.organizations.first(), title="Need sign-off", description="why", requester=requester, approver=approver)
        client = APIClient()
        client.force_authenticate(user=outsider)
        res = client.post(f"/api/approvals/{approval.id}/decide/", {"status": "approved"}, format="json")
        # get_queryset scopes Approval to requester/approver only, so an
        # outsider can't even see the object to be denied on it — 404, not 403.
        assert res.status_code == 404

@pytest.mark.django_db
class TestTaskMineFilter:
    """`?mine=true` narrows to the caller's own tasks.

    The sidebar shows five personal tasks. Without server-side filtering it
    fetched every task the user can see and sliced client-side, so the payload
    grew with the organization while the rendered output stayed at five rows.
    """

    def test_mine_returns_only_created_or_assigned(self, api_user, organization):
        User = get_user_model()
        other = User.objects.create_user(email="task-other@example.com", password="x")
        OrganizationMembership.objects.get_or_create(organization=organization, user=other)

        mine_created = Task.objects.create(
            organization=organization, title="I made this", created_by=api_user
        )
        mine_assigned = Task.objects.create(
            organization=organization, title="Assigned to me",
            created_by=other, assignee=api_user,
        )
        theirs = Task.objects.create(
            organization=organization, title="Not mine", created_by=other
        )

        client = APIClient()
        client.force_authenticate(api_user)
        resp = client.get("/api/tasks/?mine=true")

        assert resp.status_code == 200, resp.content
        # Task.id is undashed hex (uuid7 default) while the API returns the
        # canonical dashed form — compare on the hex form.
        def hexid(v):
            return str(v).replace("-", "")

        returned = {hexid(t["id"]) for t in _response_rows(resp)}
        assert hexid(mine_created.id) in returned
        assert hexid(mine_assigned.id) in returned
        assert hexid(theirs.id) not in returned

    def test_without_mine_the_scope_is_unchanged(self, api_user, organization):
        User = get_user_model()
        other = User.objects.create_user(email="task-other2@example.com", password="x")
        OrganizationMembership.objects.get_or_create(organization=organization, user=other)
        Task.objects.create(organization=organization, title="Mine", created_by=api_user)

        client = APIClient()
        client.force_authenticate(api_user)
        with_filter = client.get("/api/tasks/?mine=true")
        without = client.get("/api/tasks/")

        assert with_filter.status_code == without.status_code == 200
        # The filter must narrow, never widen.
        assert len(_response_rows(with_filter)) <= len(_response_rows(without))

    def test_mine_false_is_not_treated_as_true(self, api_user, organization):
        User = get_user_model()
        other = User.objects.create_user(email="task-other3@example.com", password="x")
        OrganizationMembership.objects.get_or_create(organization=organization, user=other)
        Task.objects.create(organization=organization, title="Mine", created_by=api_user)

        client = APIClient()
        client.force_authenticate(api_user)
        resp = client.get("/api/tasks/?mine=false")

        assert resp.status_code == 200, resp.content
        # Same as omitting the param entirely.
        assert len(_response_rows(resp)) == len(_response_rows(client.get("/api/tasks/")))

    def test_limit_caps_the_response(self, api_user, organization):
        for i in range(8):
            Task.objects.create(organization=organization, title=f"T{i}", created_by=api_user)

        client = APIClient()
        client.force_authenticate(api_user)
        resp = client.get("/api/tasks/?mine=true&limit=5")

        assert resp.status_code == 200, resp.content
        assert len(resp.data) == 5

    def test_invalid_limit_is_ignored_not_fatal(self, api_user, organization):
        Task.objects.create(organization=organization, title="T", created_by=api_user)

        client = APIClient()
        client.force_authenticate(api_user)
        resp = client.get("/api/tasks/?limit=abc")

        assert resp.status_code == 200, resp.content
