"""Tests for issue group endpoints."""

from unittest.mock import patch

# Valid UUIDs for path parameters that require UUID validation
_GID1 = "00000000-0000-4000-8000-000000000001"
_GID2 = "00000000-0000-4000-8000-000000000002"
_GID3 = "00000000-0000-4000-8000-000000000003"
_MID1 = "00000000-0000-4000-8000-000000000100"
_EID1 = "00000000-0000-4000-8000-000000000200"
_MRGID = "00000000-0000-4000-8000-000000001000"

SAMPLE_GROUP = {
    "id": _GID1,
    "org_id": "org_test456",
    "title": "NullPointerException in UserService",
}

SAMPLE_GROUP_OTHER_ORG = {
    "id": _GID2,
    "org_id": "org_other999",
    "title": "Other org's group",
}

SAMPLE_MEMBER = {
    "id": _MID1,
    "error_id": _EID1,
    "group_id": _GID1,
    "created_at": "2025-01-15T10:00:00Z",
}

SAMPLE_MERGE = {
    "id": _MRGID,
    "org_id": "org_test456",
    "source_group_id": _GID1,
    "target_group_id": _GID3,
    "source_group_title": "Source Group",
    "target_group_title": "Target Group",
    "moved_member_ids": [_MID1],
    "reason": "manual merge",
    "rolled_back_at": None,
}


class TestListIssueGroups:
    def test_list_groups_success(self, client, auth_header):
        with patch("api.db.list_issue_groups") as mock, \
             patch("api.db.count_members_by_groups") as mock_count:
            mock.return_value = {"groups": [SAMPLE_GROUP], "total": 1}
            mock_count.return_value = {_GID1: 3}
            response = client.get("/v1/issue-groups", headers=auth_header)
            assert response.status_code == 200
            data = response.json()
            assert len(data["groups"]) == 1
            assert data["groups"][0]["id"] == _GID1
            assert data["groups"][0]["member_count"] == 3
            assert data["total"] == 1
            assert data["page"] == 1
            mock.assert_called_once_with("org_test456", page=1, per_page=50)

    def test_list_groups_empty(self, client, auth_header):
        with patch("api.db.list_issue_groups") as mock:
            mock.return_value = {"groups": [], "total": 0}
            response = client.get("/v1/issue-groups", headers=auth_header)
            assert response.status_code == 200
            assert response.json()["groups"] == []
            assert response.json()["total"] == 0

    def test_list_groups_no_auth(self, client):
        response = client.get("/v1/issue-groups")
        assert response.status_code == 401


class TestGetIssueGroup:
    def test_get_group_success(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock:
            mock.return_value = SAMPLE_GROUP
            response = client.get(f"/v1/issue-groups/{_GID1}", headers=auth_header)
            assert response.status_code == 200
            assert response.json()["group"]["id"] == _GID1

    def test_get_group_not_found(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock:
            mock.return_value = None
            response = client.get(f"/v1/issue-groups/{_GID1}", headers=auth_header)
            assert response.status_code == 404

    def test_get_group_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock:
            mock.return_value = SAMPLE_GROUP_OTHER_ORG
            response = client.get(f"/v1/issue-groups/{_GID2}", headers=auth_header)
            assert response.status_code == 404

    def test_get_group_no_auth(self, client):
        response = client.get(f"/v1/issue-groups/{_GID1}")
        assert response.status_code == 401


class TestListIssueGroupMembers:
    def test_list_members_success(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group, \
             patch("api.db.list_issue_group_members") as mock_members:
            mock_group.return_value = SAMPLE_GROUP
            mock_members.return_value = [SAMPLE_MEMBER]
            response = client.get(f"/v1/issue-groups/{_GID1}/members", headers=auth_header)
            assert response.status_code == 200
            assert len(response.json()["members"]) == 1

    def test_list_members_group_not_found(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.return_value = None
            response = client.get(f"/v1/issue-groups/{_GID1}/members", headers=auth_header)
            assert response.status_code == 404

    def test_list_members_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.return_value = SAMPLE_GROUP_OTHER_ORG
            response = client.get(f"/v1/issue-groups/{_GID2}/members", headers=auth_header)
            assert response.status_code == 404

    def test_list_members_no_auth(self, client):
        response = client.get(f"/v1/issue-groups/{_GID1}/members")
        assert response.status_code == 401


class TestDeleteIssueGroup:
    def test_delete_group_success(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group, \
             patch("api.db.delete_issue_group") as mock_delete:
            mock_group.return_value = SAMPLE_GROUP
            response = client.delete(f"/v1/issue-groups/{_GID1}", headers=auth_header)
            assert response.status_code == 200
            assert response.json()["status"] == "deleted"
            mock_delete.assert_called_once_with(_GID1)

    def test_delete_group_not_found(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.return_value = None
            response = client.delete(f"/v1/issue-groups/{_GID1}", headers=auth_header)
            assert response.status_code == 404

    def test_delete_group_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.return_value = SAMPLE_GROUP_OTHER_ORG
            response = client.delete(f"/v1/issue-groups/{_GID2}", headers=auth_header)
            assert response.status_code == 404

    def test_delete_group_no_auth(self, client):
        response = client.delete(f"/v1/issue-groups/{_GID1}")
        assert response.status_code == 401


class TestDeleteIssueGroupMember:
    def test_delete_member_success(self, client, auth_header):
        with patch("api.db.get_issue_group_member") as mock_member, \
             patch("api.db.get_issue_group") as mock_group, \
             patch("api.db.delete_issue_group_member") as mock_delete:
            mock_member.return_value = SAMPLE_MEMBER
            mock_group.return_value = SAMPLE_GROUP
            response = client.delete(f"/v1/issue-group-members/{_MID1}", headers=auth_header)
            assert response.status_code == 200
            assert response.json()["status"] == "deleted"
            mock_delete.assert_called_once_with(_MID1)

    def test_delete_member_not_found(self, client, auth_header):
        with patch("api.db.get_issue_group_member") as mock_member:
            mock_member.return_value = None
            response = client.delete(f"/v1/issue-group-members/{_MID1}", headers=auth_header)
            assert response.status_code == 404

    def test_delete_member_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group_member") as mock_member, \
             patch("api.db.get_issue_group") as mock_group:
            mock_member.return_value = {**SAMPLE_MEMBER, "group_id": _GID2}
            mock_group.return_value = SAMPLE_GROUP_OTHER_ORG
            response = client.delete(f"/v1/issue-group-members/{_MID1}", headers=auth_header)
            assert response.status_code == 404

    def test_delete_member_no_auth(self, client):
        response = client.delete(f"/v1/issue-group-members/{_MID1}")
        assert response.status_code == 401


class TestMergeIssueGroup:
    def test_merge_success(self, client, auth_header):
        target_group = {"id": _GID3, "org_id": "org_test456", "title": "Target Group"}
        with patch("api.db.get_issue_group") as mock_group, \
             patch("api.db.list_issue_group_members") as mock_members, \
             patch("api.db.create_issue_group_merge") as mock_merge, \
             patch("api.db.move_issue_group_members_by_ids"), \
             patch("api.db.delete_issue_group"):
            mock_group.side_effect = lambda gid: SAMPLE_GROUP if gid == _GID1 else target_group
            mock_members.return_value = [SAMPLE_MEMBER]
            mock_merge.return_value = {"id": _MRGID}
            response = client.post(
                f"/v1/issue-groups/{_GID1}/merge",
                headers=auth_header,
                json={"target_group_id": _GID3},
            )
            assert response.status_code == 200
            assert response.json()["status"] == "merged"

    def test_merge_self_merge_rejected(self, client, auth_header):
        response = client.post(
            f"/v1/issue-groups/{_GID1}/merge",
            headers=auth_header,
            json={"target_group_id": _GID1},
        )
        assert response.status_code == 400
        assert "itself" in response.json()["detail"].lower()

    def test_merge_missing_target(self, client, auth_header):
        response = client.post(
            f"/v1/issue-groups/{_GID1}/merge",
            headers=auth_header,
            json={},
        )
        assert response.status_code == 400

    def test_merge_source_not_found(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.return_value = None
            response = client.post(
                f"/v1/issue-groups/{_GID1}/merge",
                headers=auth_header,
                json={"target_group_id": _GID3},
            )
            assert response.status_code == 404

    def test_merge_source_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.return_value = SAMPLE_GROUP_OTHER_ORG
            response = client.post(
                f"/v1/issue-groups/{_GID2}/merge",
                headers=auth_header,
                json={"target_group_id": _GID3},
            )
            assert response.status_code == 404

    def test_merge_target_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group") as mock_group:
            mock_group.side_effect = lambda gid: SAMPLE_GROUP if gid == _GID1 else SAMPLE_GROUP_OTHER_ORG
            response = client.post(
                f"/v1/issue-groups/{_GID1}/merge",
                headers=auth_header,
                json={"target_group_id": _GID2},
            )
            assert response.status_code == 404

    def test_merge_no_auth(self, client):
        response = client.post(
            f"/v1/issue-groups/{_GID1}/merge",
            json={"target_group_id": _GID3},
        )
        assert response.status_code == 401


class TestListIssueGroupMerges:
    def test_list_merges_success(self, client, auth_header):
        with patch("api.db.list_issue_group_merges") as mock:
            mock.return_value = [SAMPLE_MERGE]
            response = client.get(f"/v1/issue-groups/{_GID1}/merges", headers=auth_header)
            assert response.status_code == 200
            assert len(response.json()["merges"]) == 1
            mock.assert_called_once_with("org_test456", group_id=_GID1)


class TestRollbackMerge:
    def test_rollback_success(self, client, auth_header):
        with patch("api.db.get_issue_group_merge") as mock_merge, \
             patch("api.db.get_issue_group") as mock_group, \
             patch("api.db.create_issue_group"), \
             patch("api.db.move_issue_group_members_by_ids"), \
             patch("api.db.mark_issue_group_merge_rolled_back"):
            mock_merge.return_value = SAMPLE_MERGE
            mock_group.return_value = None  # source group deleted
            response = client.post(f"/v1/issue-groups/merges/{_MRGID}/rollback", headers=auth_header)
            assert response.status_code == 200
            assert response.json()["status"] == "rolled_back"

    def test_rollback_already_rolled_back(self, client, auth_header):
        with patch("api.db.get_issue_group_merge") as mock_merge:
            mock_merge.return_value = {**SAMPLE_MERGE, "rolled_back_at": "2025-01-15T12:00:00Z"}
            response = client.post(f"/v1/issue-groups/merges/{_MRGID}/rollback", headers=auth_header)
            assert response.status_code == 200
            assert response.json()["status"] == "already_rolled_back"

    def test_rollback_wrong_org(self, client, auth_header):
        with patch("api.db.get_issue_group_merge") as mock_merge:
            mock_merge.return_value = {**SAMPLE_MERGE, "org_id": "org_other999"}
            response = client.post(f"/v1/issue-groups/merges/{_MRGID}/rollback", headers=auth_header)
            assert response.status_code == 404

    def test_rollback_not_found(self, client, auth_header):
        with patch("api.db.get_issue_group_merge") as mock_merge:
            mock_merge.return_value = None
            response = client.post(f"/v1/issue-groups/merges/{_MRGID}/rollback", headers=auth_header)
            assert response.status_code == 404


class TestIssueGroupStats:
    def test_stats_success(self, client, auth_header):
        with patch("api.db.get_issue_group_stats") as mock:
            mock.return_value = {"total_groups": 5, "merge_count": 2, "rollback_count": 1}
            response = client.get("/v1/issue-groups/stats", headers=auth_header)
            assert response.status_code == 200
            data = response.json()
            assert data["total_groups"] == 5
            assert data["merge_count"] == 2
            assert data["rollback_count"] == 1
            mock.assert_called_once_with("org_test456")
