"""Tests for add_comment.py module."""

import io
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, Mock, mock_open, patch

import pytest
import requests
import typer

from jps_jira_utils.add_comment import (
    build_comment_body,
    confirm_comment,
    get_attachment_note,
    get_comment_text,
    post_comment,
    upload_attachment,
    validate_and_prepare_attachment,
)


class TestConfirmComment:
    """Tests for confirm_comment function."""

    def test_confirm_comment_accepts(self, monkeypatch):
        """Test user confirms comment."""
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: True)
        assert confirm_comment("Test comment") is True

    def test_confirm_comment_rejects(self, monkeypatch):
        """Test user rejects comment."""
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: False)
        assert confirm_comment("Test comment") is False

    def test_confirm_comment_empty_text(self, monkeypatch):
        """Test confirmation with empty comment text."""
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: True)
        assert confirm_comment("") is True


class TestGetAttachmentNote:
    """Tests for get_attachment_note function."""

    def test_canned_note_by_number(self, monkeypatch):
        """Test selecting canned note by number."""
        inputs = iter([True, "1"])
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: next(inputs))
        monkeypatch.setattr("typer.prompt", lambda *args, **kwargs: next(inputs))
        
        result = get_attachment_note()
        assert result == "This README.md outlines steps for testing and evaluation."

    def test_canned_note_by_partial_text(self, monkeypatch):
        """Test selecting canned note by partial text match."""
        inputs = iter([True, "STDOUT"])
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: next(inputs))
        monkeypatch.setattr("typer.prompt", lambda *args, **kwargs: next(inputs))
        
        result = get_attachment_note()
        assert result == "This is the STDOUT."

    def test_canned_note_invalid_then_valid(self, monkeypatch):
        """Test invalid selection followed by valid one."""
        inputs = iter([True, "99", "2"])
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: next(inputs))
        monkeypatch.setattr("typer.prompt", lambda *args, **kwargs: next(inputs))
        
        result = get_attachment_note()
        assert result == "This is the STDOUT."

    def test_custom_note(self, monkeypatch):
        """Test entering custom note."""
        inputs = iter([False, "Custom attachment note"])
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: next(inputs))
        monkeypatch.setattr("typer.prompt", lambda *args, **kwargs: next(inputs))
        
        result = get_attachment_note()
        assert result == "Custom attachment note"


class TestGetCommentText:
    """Tests for get_comment_text function."""

    def test_from_file(self, tmp_path, caplog):
        """Test loading comment from file."""
        comment_file = tmp_path / "comment.txt"
        comment_file.write_text("Test comment from file")
        logger = Mock()
        
        result = get_comment_text(comment_file, logger)
        
        assert result == "Test comment from file"
        logger.info.assert_called()

    def test_file_not_found(self, tmp_path, caplog):
        """Test error when comment file doesn't exist."""
        comment_file = tmp_path / "nonexistent.txt"
        logger = Mock()
        
        with pytest.raises(typer.Exit):
            get_comment_text(comment_file, logger)
        
        logger.error.assert_called()

    def test_interactive_input_confirmed(self, monkeypatch):
        """Test interactive comment input with confirmation."""
        logger = Mock()
        monkeypatch.setattr(
            "jps_jira_utils.add_comment.read_multiline_input",
            lambda *args: "Interactive comment"
        )
        monkeypatch.setattr(
            "jps_jira_utils.add_comment.confirm_comment",
            lambda *args: True
        )
        
        result = get_comment_text(None, logger)
        assert result == "Interactive comment"

    def test_interactive_input_cancelled(self, monkeypatch):
        """Test interactive comment input cancelled by user."""
        logger = Mock()
        monkeypatch.setattr(
            "jps_jira_utils.add_comment.read_multiline_input",
            lambda *args: "Interactive comment"
        )
        monkeypatch.setattr(
            "jps_jira_utils.add_comment.confirm_comment",
            lambda *args: False
        )
        
        with pytest.raises(typer.Exit) as exc_info:
            get_comment_text(None, logger)
        
        assert exc_info.value.exit_code == 0


class TestValidateAndPrepareAttachment:
    """Tests for validate_and_prepare_attachment function."""

    def test_regular_file(self, tmp_path):
        """Test validation of regular file."""
        attach_file = tmp_path / "test.txt"
        attach_file.write_text("Test content")
        logger = Mock()
        
        result_path, timestamp_name = validate_and_prepare_attachment(attach_file, logger)
        
        assert result_path == attach_file
        assert timestamp_name is None

    def test_readme_creates_timestamped_copy(self, tmp_path):
        """Test README.md creates timestamped copy."""
        readme = tmp_path / "README.md"
        readme.write_text("README content")
        logger = Mock()
        
        with patch("jps_jira_utils.add_comment.datetime") as mock_dt:
            mock_dt.now.return_value.strftime.return_value = "2024-01-15-1430"
            result_path, timestamp_name = validate_and_prepare_attachment(readme, logger)
        
        assert timestamp_name == "README-2024-01-15-1430.md"
        assert result_path.name == "README-2024-01-15-1430.md"
        assert result_path.exists()

    def test_file_not_found(self, tmp_path):
        """Test error when attachment file doesn't exist."""
        attach_file = tmp_path / "nonexistent.txt"
        logger = Mock()
        
        with pytest.raises(typer.Exit):
            validate_and_prepare_attachment(attach_file, logger)
        
        logger.error.assert_called()

    def test_not_regular_file(self, tmp_path):
        """Test error when attachment is not a regular file."""
        attach_file = tmp_path  # directory, not file
        logger = Mock()
        
        with pytest.raises(typer.Exit):
            validate_and_prepare_attachment(attach_file, logger)

    def test_empty_file(self, tmp_path):
        """Test error when attachment file is empty."""
        attach_file = tmp_path / "empty.txt"
        attach_file.touch()
        logger = Mock()
        
        with pytest.raises(typer.Exit):
            validate_and_prepare_attachment(attach_file, logger)


class TestBuildCommentBody:
    """Tests for build_comment_body function."""

    def test_comment_only(self):
        """Test with comment text only."""
        result = build_comment_body("Test comment", None, None)
        assert result == "Test comment"

    def test_comment_with_readme_timestamp(self):
        """Test comment with README timestamp."""
        result = build_comment_body("Test comment", "README-2024-01-15-1430.md", None)
        expected = "Attached README-2024-01-15-1430.md.\n\nTest comment"
        assert result == expected

    def test_comment_with_attachment_note(self):
        """Test comment with attachment note."""
        result = build_comment_body("Test comment", None, "This is the log file.")
        expected = "Test comment\n\nThis is the log file."
        assert result == expected

    def test_all_components(self):
        """Test with all components."""
        result = build_comment_body(
            "Test comment",
            "README-2024-01-15-1430.md",
            "This is the log file."
        )
        expected = "Attached README-2024-01-15-1430.md.\n\nTest comment\n\nThis is the log file."
        assert result == expected

    def test_empty_comment_with_readme(self):
        """Test empty comment with README timestamp."""
        result = build_comment_body("", "README-2024-01-15-1430.md", None)
        assert result == "Attached README-2024-01-15-1430.md."

    def test_multiline_comment_stripped(self):
        """Test multiline comment is stripped of trailing newlines."""
        result = build_comment_body("Line 1\nLine 2\n\n\n", None, None)
        assert result == "Line 1\nLine 2"


class TestPostComment:
    """Tests for post_comment function."""

    def test_successful_post(self):
        """Test successful comment posting."""
        session = Mock()
        response = Mock()
        response.ok = True
        response.json.return_value = {"id": "12345"}
        session.post.return_value = response
        logger = Mock()
        
        comment_id = post_comment(
            session, "https://jira.example.com", "JPS-123", "Test comment", logger
        )
        
        assert comment_id == "12345"
        session.post.assert_called_once()

    def test_failed_post(self):
        """Test failed comment posting."""
        session = Mock()
        response = Mock()
        response.ok = False
        response.status_code = 400
        response.text = "Bad request"
        session.post.return_value = response
        logger = Mock()
        
        with pytest.raises(typer.Exit):
            post_comment(
                session, "https://jira.example.com", "JPS-123", "Test comment", logger
            )
        
        logger.error.assert_called()

    def test_correct_payload_structure(self):
        """Test that payload has correct ADF structure."""
        session = Mock()
        response = Mock()
        response.ok = True
        response.json.return_value = {"id": "12345"}
        session.post.return_value = response
        logger = Mock()
        
        post_comment(
            session, "https://jira.example.com", "JPS-123", "Test comment", logger
        )
        
        call_args = session.post.call_args
        payload = call_args[1]["json"]
        assert payload["body"]["type"] == "doc"
        assert payload["body"]["version"] == 1
        assert payload["body"]["content"][0]["type"] == "paragraph"


class TestUploadAttachment:
    """Tests for upload_attachment function."""

    def test_successful_upload(self, tmp_path):
        """Test successful attachment upload."""
        attach_file = tmp_path / "test.txt"
        attach_file.write_text("Test content")
        
        session = Mock()
        response = Mock()
        response.ok = True
        session.post.return_value = response
        logger = Mock()
        
        upload_attachment(
            session, "https://jira.example.com", "JPS-123", attach_file, logger
        )
        
        session.post.assert_called_once()
        call_args = session.post.call_args
        assert "X-Atlassian-Token" in call_args[1]["headers"]

    def test_failed_upload(self, tmp_path):
        """Test failed attachment upload (should log warning, not raise)."""
        attach_file = tmp_path / "test.txt"
        attach_file.write_text("Test content")
        
        session = Mock()
        response = Mock()
        response.ok = False
        response.status_code = 413
        response.text = "File too large"
        session.post.return_value = response
        logger = Mock()
        
        # Should not raise, just log error
        upload_attachment(
            session, "https://jira.example.com", "JPS-123", attach_file, logger
        )
        
        logger.error.assert_called()


class TestMainIntegration:
    """Integration tests for main function."""

    @patch("jps_jira_utils.add_comment.setup_logging")
    @patch("jps_jira_utils.add_comment.load_jira_env")
    @patch("jps_jira_utils.add_comment.requests.Session")
    def test_main_comment_only(self, mock_session, mock_env, mock_logging, tmp_path, monkeypatch):
        """Test main function with comment only."""
        # Setup mocks
        mock_env.return_value = ("https://jira.example.com", "user@example.com", "token")
        mock_logger = Mock()
        mock_logging.return_value = mock_logger
        
        session = Mock()
        response = Mock()
        response.ok = True
        response.json.return_value = {"id": "12345"}
        session.post.return_value = response
        mock_session.return_value = session
        
        # Mock user input
        monkeypatch.setattr(
            "jps_jira_utils.add_comment.read_multiline_input",
            lambda *args: "Test comment"
        )
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: True)
        
        from jps_jira_utils.add_comment import main
        
        # Run main
        main("JPS-123", None, None, None, None)
        
        # Verify comment was posted
        assert session.post.call_count >= 1

    @patch("jps_jira_utils.add_comment.setup_logging")
    @patch("jps_jira_utils.add_comment.load_jira_env")
    @patch("jps_jira_utils.add_comment.requests.Session")
    def test_main_with_attachment(self, mock_session, mock_env, mock_logging, tmp_path, monkeypatch):
        """Test main function with attachment."""
        # Setup mocks
        mock_env.return_value = ("https://jira.example.com", "user@example.com", "token")
        mock_logger = Mock()
        mock_logging.return_value = mock_logger
        
        session = Mock()
        response = Mock()
        response.ok = True
        response.json.return_value = {"id": "12345"}
        session.post.return_value = response
        mock_session.return_value = session
        
        # Create test attachment
        attach_file = tmp_path / "test.txt"
        attach_file.write_text("Test content")
        
        # Mock user input
        monkeypatch.setattr(
            "jps_jira_utils.add_comment.read_multiline_input",
            lambda *args: "Test comment"
        )
        confirms = iter([True, False, True])  # confirm comment, skip note, final confirm
        monkeypatch.setattr("typer.confirm", lambda *args, **kwargs: next(confirms))
        
        from jps_jira_utils.add_comment import main
        
        # Run main
        main("JPS-123", None, attach_file, None, None)
        
        # Verify both comment and attachment were posted
        assert session.post.call_count >= 2
