import unittest
import pytest
from unittest.mock import patch, MagicMock
from jira import JIRAError
from uuid import uuid4

from preloop.sync.trackers.jira import JiraTracker
from preloop.models.models import Webhook


@pytest.mark.asyncio
class TestJiraTrackerWebhooks(unittest.IsolatedAsyncioTestCase):
    def setUp(self):
        self.mock_jira_patcher = patch("preloop.sync.trackers.jira.JIRA")
        self.mock_crud_patcher = patch("preloop.sync.trackers.jira.crud_webhook")

        self.mock_jira_class = self.mock_jira_patcher.start()
        self.mock_crud_webhook = self.mock_crud_patcher.start()

        self.mock_jira_client = MagicMock()
        self.mock_jira_class.return_value = self.mock_jira_client

        self.mock_db_session = MagicMock()
        self.test_tracker_id = str(uuid4())

        self.tracker = JiraTracker(
            tracker_id=self.test_tracker_id,
            api_key="fake_key",
            connection_details={
                "jira_url": "https://myjira.atlassian.net",
                "username": "user@example.com",
            },
        )

    def tearDown(self):
        self.mock_jira_patcher.stop()
        self.mock_crud_patcher.stop()

    async def test_register_webhook_success(self):
        """Test successful webhook registration when none exists in DB."""
        self.mock_crud_webhook.get_by_project_id.return_value = None
        mock_response = MagicMock()
        mock_response.json.return_value = {"id": "12345"}
        self.mock_jira_client._session.post.return_value = mock_response
        mock_project = MagicMock()
        mock_project.id = "proj-db-id"
        mock_project.identifier = "TEST"
        result = self.tracker.register_webhook(
            db=self.mock_db_session,
            project=mock_project,
            webhook_url="https://example.com/webhook",
            secret="mysecret",
        )

        self.assertTrue(result)
        self.mock_crud_webhook.get_by_project_id.assert_called_once_with(
            self.mock_db_session, project_id="proj-db-id"
        )
        self.mock_jira_client._session.post.assert_called_once()
        self.mock_crud_webhook.create.assert_called_once()

    async def test_register_webhook_already_in_db(self):
        """Test webhook registration is skipped if already in DB."""
        self.mock_crud_webhook.get_by_project_id.return_value = Webhook(
            id="wh-id", project_id="proj-db-id", external_id="123"
        )
        mock_project = MagicMock()
        mock_project.id = "proj-db-id"
        mock_project.identifier = "TEST"
        result = self.tracker.register_webhook(
            db=self.mock_db_session,
            project=mock_project,
            webhook_url="https://example.com/webhook",
            secret="mysecret",
        )

        self.assertTrue(result)
        self.mock_crud_webhook.get_by_project_id.assert_called_once_with(
            self.mock_db_session, project_id="proj-db-id"
        )
        self.mock_jira_client._session.post.assert_not_called()
        self.mock_crud_webhook.create.assert_not_called()

    async def test_unregister_webhook_success(self):
        """Test successful unregistration of an existing webhook."""
        mock_webhook = Webhook(
            id="wh-db-id", project_id="proj-db-id", external_id="12345"
        )
        self.mock_jira_client._session.delete.return_value = MagicMock(status_code=204)
        result = self.tracker.unregister_webhook(
            db=self.mock_db_session, webhook=mock_webhook
        )

        self.assertTrue(result)
        self.mock_jira_client._session.delete.assert_called_once_with(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook/12345"
        )
        self.mock_crud_webhook.remove.assert_called_once_with(
            self.mock_db_session, id="wh-db-id"
        )

    async def test_unregister_webhook_not_in_jira(self):
        """Test unregistration when webhook is in DB but not in Jira (404)."""
        mock_webhook = Webhook(
            id="wh-db-id", project_id="proj-db-id", external_id="12345"
        )
        self.mock_jira_client._session.delete.side_effect = JIRAError(
            status_code=404, text="Not Found"
        )
        result = self.tracker.unregister_webhook(
            db=self.mock_db_session, webhook=mock_webhook
        )

        self.assertTrue(result)
        self.mock_jira_client._session.delete.assert_called_once_with(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook/12345"
        )
        self.mock_crud_webhook.remove.assert_called_once_with(
            self.mock_db_session, id="wh-db-id"
        )

    async def test_cleanup_stale_webhooks(self):
        """Test cleaning up stale webhooks."""
        preloop_url = "https://stale-preloop.com"
        mock_webhooks_data = [
            {"id": "1", "url": f"{preloop_url}/webhook"},
            {"id": "2", "url": "https://another-service.com/webhook"},
            {"id": "3", "url": f"{preloop_url}/another_webhook"},
        ]
        mock_response = MagicMock()
        mock_response.json.return_value = mock_webhooks_data
        self.mock_jira_client._session.get.return_value = mock_response
        result = self.tracker.cleanup_stale_webhooks(preloop_url)
        self.assertEqual(result, {"unregistered": 2, "failed": 0})
        self.mock_jira_client._session.get.assert_called_once_with(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook"
        )
        self.mock_jira_client._session.delete.assert_any_call(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook/1"
        )
        self.mock_jira_client._session.delete.assert_any_call(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook/3"
        )
        self.assertEqual(self.mock_jira_client._session.delete.call_count, 2)

    async def test_cleanup_stale_webhooks_with_failures(self):
        """Test cleanup with some deletions failing."""
        preloop_url = "https://stale-preloop.com"
        mock_webhooks_data = [
            {"id": "1", "url": f"{preloop_url}/webhook"},
            {"id": "2", "url": f"{preloop_url}/failing"},
        ]
        mock_response = MagicMock()
        mock_response.json.return_value = mock_webhooks_data
        self.mock_jira_client._session.get.return_value = mock_response
        self.mock_jira_client._session.delete.side_effect = [
            MagicMock(status_code=204),
            JIRAError(status_code=500, text="Internal Server Error"),
        ]
        result = self.tracker.cleanup_stale_webhooks(preloop_url)
        self.assertEqual(result, {"unregistered": 1, "failed": 1})
        self.mock_jira_client._session.get.assert_called_once_with(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook"
        )
        self.mock_jira_client._session.delete.assert_any_call(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook/1"
        )
        self.mock_jira_client._session.delete.assert_any_call(
            "https://myjira.atlassian.net/rest/webhooks/1.0/webhook/2"
        )
        self.assertEqual(self.mock_jira_client._session.delete.call_count, 2)


@pytest.mark.asyncio
class TestJiraTracker(unittest.IsolatedAsyncioTestCase):
    @patch("preloop.sync.trackers.jira.JIRA")
    async def test_get_organizations(self, mock_jira_class):
        tracker = JiraTracker(
            "tracker-1",
            "api-key",
            {"url": "https://test.jira.com", "username": "testuser"},
        )
        orgs = await tracker.get_organizations()

        self.assertEqual(len(orgs), 1)
        self.assertEqual(orgs[0]["name"], "test.jira.com")

    @patch("preloop.sync.trackers.jira.JIRA")
    async def test_get_projects(self, mock_jira_class):
        tracker = JiraTracker(
            "tracker-1",
            "api-key",
            {"url": "https://test.jira.com", "username": "testuser"},
        )
        with patch.object(
            tracker,
            "_make_request",
            return_value=[
                {
                    "id": "10000",
                    "key": "PROJ",
                    "name": "Test Project",
                    "description": "A test project",
                }
            ],
        ) as mock_make_request:
            projects = await tracker.get_projects("org-1")

            self.assertEqual(len(projects), 1)
            self.assertEqual(projects[0]["name"], "Test Project")
            mock_make_request.assert_called_once_with("GET", "project")
