"""
Tests for IssueCreator functionality.
"""

import json
import subprocess
import pytest
from unittest.mock import patch, MagicMock, mock_open
from pathlib import Path

from tddai.issue_creator import IssueCreator
from tddai.exceptions import IssueError
from tddai.config import TddaiConfig


class TestIssueCreator:
    """Test suite for IssueCreator class."""

    def _get_test_config(self):
        """Get a valid test configuration."""
        return TddaiConfig(
            workspace_dir=Path(".test_workspace"),
            gitea_url="http://localhost:3000",
            repo_owner="test_owner",
            repo_name="test_repo"
        )

    def _get_complete_mock_response(self, number: int, title: str = "Test Issue", body: str = "Test description"):
        """Get a complete mock API response with all required fields."""
        return {
            "number": number,
            "title": title,
            "body": body,
            "state": "open",
            "created_at": "2025-09-26T10:00:00Z",
            "updated_at": "2025-09-26T10:00:00Z",
            "html_url": f"http://gitea.example.com/repo/issues/{number}"
        }

    def test_issue_creator_initializes_with_authentication_token(self):
        """Test IssueCreator can be initialized with authentication token."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        assert creator.config == config
        assert creator.auth_token == "test-token"

    def test_issue_creator_reads_authentication_from_environment_variable(self):
        """Test IssueCreator reads authentication token from environment variable."""
        config = self._get_test_config()

        with patch.dict('os.environ', {'GITEA_API_TOKEN': 'env-token'}):
            creator = IssueCreator(config=config)
            assert creator.auth_token == 'env-token'

    def test_issue_creator_handles_missing_authentication_token_gracefully(self):
        """Test IssueCreator handles missing authentication token gracefully."""
        config = self._get_test_config()

        # Ensure no environment token interferes
        with patch.dict('os.environ', {}, clear=True):
            creator = IssueCreator(config=config)

        assert creator.auth_token is None

    @patch('subprocess.run')
    def test_create_issue_success(self, mock_run):
        """Test successful issue creation."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        # Mock successful API response
        mock_response = {
            "number": 123,
            "title": "Test Issue",
            "body": "Test description",
            "state": "open",
            "created_at": "2025-09-26T10:00:00Z",
            "updated_at": "2025-09-26T10:00:00Z",
            "html_url": "http://gitea.example.com/repo/issues/123"
        }

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=json.dumps(mock_response),
            stderr=""
        )

        result = creator.create_issue("Test Issue", "Test description")

        # Verify the result has the expected structure (transformed by issue creator)
        expected_result = {
            'number': 123,
            'title': "Test Issue",
            'body': "Test description",
            'state': "open",
            'html_url': "http://gitea.example.com/repo/issues/123",
            'created_at': "2025-09-26T10:00:00",  # ISO format from datetime parsing
            'updated_at': "2025-09-26T10:00:00",
            'assignee': None,
            'labels': []
        }
        assert result == expected_result
        mock_run.assert_called_once()

        # Check curl command structure
        call_args = mock_run.call_args[0][0]
        assert 'curl' in call_args
        assert '-X' in call_args
        assert 'POST' in call_args
        assert 'Authorization: token test-token' in ' '.join(call_args)

    def test_create_issue_without_auth_token(self):
        """Test issue creation without authentication token."""
        config = self._get_test_config()

        # Ensure no environment token interferes
        with patch.dict('os.environ', {}, clear=True):
            creator = IssueCreator(config=config)
            with pytest.raises(IssueError, match="Authentication token required"):
                creator.create_issue("Test Issue", "Test description")

    def test_create_issue_empty_title(self):
        """Test issue creation with empty title."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        with pytest.raises(IssueError, match="Issue title cannot be empty"):
            creator.create_issue("", "Test description")

        with pytest.raises(IssueError, match="Issue title cannot be empty"):
            creator.create_issue("   ", "Test description")

    @patch('subprocess.run')
    def test_create_issue_api_error(self, mock_run):
        """Test issue creation with API error response."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        # Mock API error response
        mock_response = {"message": "Repository not found"}
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=json.dumps(mock_response),
            stderr=""
        )

        with pytest.raises(IssueError, match="Failed to create issue: Repository not found"):
            creator.create_issue("Test Issue", "Test description")

    @patch('subprocess.run')
    def test_create_issue_subprocess_error(self, mock_run):
        """Test issue creation with subprocess error."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        mock_run.side_effect = subprocess.CalledProcessError(1, 'curl')

        with pytest.raises(IssueError, match="Failed to create issue"):
            creator.create_issue("Test Issue", "Test description")

    @patch('subprocess.run')
    def test_create_issue_json_error(self, mock_run):
        """Test issue creation with invalid JSON response."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="invalid json",
            stderr=""
        )

        with pytest.raises(IssueError, match="Failed to create issue.*parse.*response"):
            creator.create_issue("Test Issue", "Test description")

    @patch('gitea.http_client.subprocess.run')
    def test_create_issue_with_optional_fields(self, mock_run):
        """Test issue creation with optional fields."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        # Mock labels API response for label resolution
        labels_response = [
            {"id": 1, "name": "bug", "color": "red"},
            {"id": 2, "name": "high", "color": "orange"}
        ]

        # Mock issue creation response
        issue_response = self._get_complete_mock_response(124)

        # Configure mock to return different responses based on URL
        def side_effect(*args, **kwargs):
            cmd = args[0]
            url = cmd[-1]  # URL is the last argument

            result_mock = MagicMock()
            result_mock.returncode = 0
            result_mock.stderr = ""

            if 'labels' in url:
                result_mock.stdout = json.dumps(labels_response)
            else:
                result_mock.stdout = json.dumps(issue_response)

            return result_mock

        mock_run.side_effect = side_effect

        result = creator.create_issue(
            "Test Issue",
            "Test description",
            assignees=["user1"],
            milestone=1,
            labels=["bug", "high"]
        )

        # Verify issue was created successfully
        assert result['number'] == 124
        assert result['title'] == "Test Issue"

        # Verify the API was called correctly
        # Find the issue creation call (not the labels call)
        create_call = None
        for call in mock_run.call_args_list:
            cmd = call[0][0]
            url = cmd[-1]
            if 'issues' in url and '/labels' not in url:
                create_call = call
                break

        assert create_call is not None
        cmd = create_call[0][0]

        # Find the -d argument (data payload)
        data_index = cmd.index('-d') + 1
        payload = json.loads(cmd[data_index])

        assert payload['assignees'] == ["user1"]
        assert payload['milestone'] == 1
        assert payload['labels'] == [1, 2]  # Should be IDs now

    @patch('gitea.http_client.subprocess.run')
    def test_create_enhancement_issue(self, mock_run):
        """Test creating enhancement issue with structured format."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        # Mock labels API response for label resolution
        labels_response = [
            {"id": 1, "name": "enhancement", "color": "blue"},
            {"id": 2, "name": "high", "color": "orange"}
        ]

        # Mock issue creation response
        issue_response = self._get_complete_mock_response(125)

        # Configure mock to return different responses based on URL
        def side_effect(*args, **kwargs):
            cmd = args[0]
            url = cmd[-1]  # URL is the last argument

            result_mock = MagicMock()
            result_mock.returncode = 0
            result_mock.stderr = ""

            if 'labels' in url:
                result_mock.stdout = json.dumps(labels_response)
            else:
                result_mock.stdout = json.dumps(issue_response)

            return result_mock

        mock_run.side_effect = side_effect

        result = creator.create_enhancement_issue(
            title="Add CLI Support",
            use_case="User needs command-line interface",
            technical_requirements="Implement Click framework",
            acceptance_criteria=["CLI entry point works", "Commands have help text"],
            dependencies=["Issue #1 - Database"],
            priority="High"
        )

        # Verify structure of created issue
        create_call = None
        for call in mock_run.call_args_list:
            cmd = call[0][0]
            url = cmd[-1]
            if 'issues' in url and '/labels' not in url:
                create_call = call
                break

        assert create_call is not None
        cmd = create_call[0][0]

        # Find the -d argument (data payload)
        data_index = cmd.index('-d') + 1
        payload = json.loads(cmd[data_index])

        assert "UseCase: User needs command-line interface" in payload['body']
        assert "Technical Requirements:" in payload['body']
        assert "- [ ] CLI entry point works" in payload['body']
        assert "- [ ] Commands have help text" in payload['body']
        assert "Dependencies:" in payload['body']
        assert "- Issue #1 - Database" in payload['body']
        assert payload['labels'] == [2, 1]  # Should be IDs: [high, enhancement]

    @patch('gitea.http_client.subprocess.run')
    def test_create_bug_issue(self, mock_run):
        """Test creating bug issue with structured format."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        # Mock labels API response for label resolution
        labels_response = [
            {"id": 1, "name": "bug", "color": "red"}
        ]

        # Mock issue creation response
        issue_response = self._get_complete_mock_response(126)

        # Configure mock to return different responses based on URL
        def side_effect(*args, **kwargs):
            cmd = args[0]
            url = cmd[-1]  # URL is the last argument

            result_mock = MagicMock()
            result_mock.returncode = 0
            result_mock.stderr = ""

            if 'labels' in url:
                result_mock.stdout = json.dumps(labels_response)
            else:
                result_mock.stdout = json.dumps(issue_response)

            return result_mock

        mock_run.side_effect = side_effect

        result = creator.create_bug_issue(
            title="CLI crashes on empty input",
            description="The CLI tool crashes when given empty input",
            steps_to_reproduce=["Run CLI command", "Provide empty input", "Observe crash"],
            expected_behavior="Should show help message",
            actual_behavior="Application crashes",
            environment="Python 3.8, Linux"
        )

        # Verify structure of created bug issue
        create_call = None
        for call in mock_run.call_args_list:
            cmd = call[0][0]
            url = cmd[-1]
            if 'issues' in url and '/labels' not in url:
                create_call = call
                break

        assert create_call is not None
        cmd = create_call[0][0]

        # Find the -d argument (data payload)
        data_index = cmd.index('-d') + 1
        payload = json.loads(cmd[data_index])

        assert "The CLI tool crashes when given empty input" in payload['body']
        assert "Steps to Reproduce:" in payload['body']
        assert "1. Run CLI command" in payload['body']
        assert "2. Provide empty input" in payload['body']
        assert "Expected Behavior: Should show help message" in payload['body']
        assert "Actual Behavior: Application crashes" in payload['body']
        assert "Environment: Python 3.8, Linux" in payload['body']
        assert payload['labels'] == [1]  # Should be ID: [bug]

    @patch('subprocess.run')
    @patch('builtins.open', new_callable=mock_open, read_data="Title: Template Issue\nTemplate body content with {variable}")
    def test_create_from_template(self, mock_file, mock_run):
        """Test creating issue from template file."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        mock_response = self._get_complete_mock_response(127)
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout=json.dumps(mock_response),
            stderr=""
        )

        result = creator.create_from_template(
            "template.md",
            variable="test value"
        )

        # Verify template processing
        call_args = mock_run.call_args[0][0]
        json_data_index = call_args.index('-d') + 1
        json_data = json.loads(call_args[json_data_index])

        assert json_data['title'] == "Template Issue"
        assert "Template body content with test value" in json_data['body']

    def test_create_from_template_file_not_found(self):
        """Test creating issue from non-existent template."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        with pytest.raises(IssueError, match="Template file not found"):
            creator.create_from_template("nonexistent.md")

    @patch('builtins.open', new_callable=mock_open, read_data="")
    def test_create_from_empty_template(self, mock_file):
        """Test creating issue from empty template."""
        config = self._get_test_config()
        creator = IssueCreator(config=config, auth_token="test-token")

        with pytest.raises(IssueError, match="Template file is empty"):
            creator.create_from_template("empty.md")