"""
Tests for BQAS Notifier Module

Tests for the local notification system that replaces GitHub Actions notifications.
"""

import json
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from unittest.mock import patch, MagicMock
import subprocess

import pytest

# Import notifier directly to avoid __init__.py dependency issues
import importlib.util
spec = importlib.util.spec_from_file_location(
    "notifier",
    Path(__file__).parent.parent.parent / "bqas" / "notifier.py"
)
notifier_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(notifier_module)

BQASNotifier = notifier_module.BQASNotifier
Notification = notifier_module.Notification
NotificationConfig = notifier_module.NotificationConfig


class TestNotificationConfig:
    """Tests for NotificationConfig dataclass."""

    def test_default_config(self):
        """Test default configuration values."""
        config = NotificationConfig()

        assert config.enabled is True
        assert config.desktop_enabled is True
        assert config.slack_enabled is False
        assert config.email_enabled is False
        assert config.log_file == "/var/log/bqas/notifications.log"

    def test_config_from_env(self):
        """Test configuration from environment variables."""
        with patch.dict(os.environ, {
            "BQAS_NOTIFY_ENABLED": "true",
            "BQAS_NOTIFY_DESKTOP": "false",
            "BQAS_NOTIFY_SLACK": "true",
            "BQAS_SLACK_WEBHOOK": "https://hooks.slack.com/test",
            "BQAS_SLACK_CHANNEL": "#test-channel",
        }):
            config = NotificationConfig.from_env()

            assert config.enabled is True
            assert config.desktop_enabled is False
            assert config.slack_enabled is True
            assert config.slack_webhook_url == "https://hooks.slack.com/test"
            assert config.slack_channel == "#test-channel"

    def test_config_disabled(self):
        """Test disabled notification configuration."""
        with patch.dict(os.environ, {"BQAS_NOTIFY_ENABLED": "false"}):
            config = NotificationConfig.from_env()
            assert config.enabled is False


class TestNotification:
    """Tests for Notification dataclass."""

    def test_notification_creation(self):
        """Test creating a notification."""
        notification = Notification(
            status="success",
            message="All tests passed",
            details="Golden: 97/97, RAG: 26/26",
        )

        assert notification.status == "success"
        assert notification.message == "All tests passed"
        assert notification.details == "Golden: 97/97, RAG: 26/26"
        assert notification.source == "bqas"
        assert notification.timestamp  # Should be auto-generated

    def test_notification_timestamp_auto(self):
        """Test that timestamp is auto-generated."""
        notification = Notification(status="failure", message="Test")

        # Timestamp should be in ISO format
        datetime.fromisoformat(notification.timestamp)

    def test_notification_statuses(self):
        """Test different notification statuses."""
        for status in ["success", "failure", "warning"]:
            notification = Notification(status=status, message="Test")
            assert notification.status == status


class TestBQASNotifier:
    """Tests for BQASNotifier class."""

    def test_notifier_creation(self):
        """Test creating a notifier instance."""
        notifier = BQASNotifier()
        assert notifier.config is not None

    def test_notifier_with_config(self):
        """Test creating notifier with custom config."""
        config = NotificationConfig(
            desktop_enabled=False,
            slack_enabled=True,
            slack_webhook_url="https://test.webhook",
        )
        notifier = BQASNotifier(config=config)

        assert notifier.config.desktop_enabled is False
        assert notifier.config.slack_enabled is True

    def test_notify_disabled(self):
        """Test that notify returns False when disabled."""
        config = NotificationConfig(enabled=False)
        notifier = BQASNotifier(config=config)

        notification = Notification(status="success", message="Test")
        result = notifier.notify(notification)

        assert result is False

    def test_log_notification(self):
        """Test logging notifications to file."""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as f:
            log_path = f.name

        try:
            config = NotificationConfig(
                enabled=True,
                desktop_enabled=False,
                log_file=log_path,
            )
            notifier = BQASNotifier(config=config)

            notification = Notification(
                status="success",
                message="Test message",
                details="Test details",
            )
            notifier._log_notification(notification)

            # Check log file contents
            with open(log_path) as f:
                log_content = f.read()
                log_entry = json.loads(log_content.strip())

                assert log_entry["status"] == "success"
                assert log_entry["message"] == "Test message"
                assert log_entry["details"] == "Test details"
                assert "logged_at" in log_entry
        finally:
            os.unlink(log_path)

    @patch("subprocess.run")
    def test_send_desktop_success(self, mock_run):
        """Test sending desktop notification."""
        mock_run.return_value = MagicMock(returncode=0)

        config = NotificationConfig(desktop_enabled=True)
        notifier = BQASNotifier(config=config)

        notification = Notification(status="success", message="Test")
        result = notifier._send_desktop(notification)

        assert result is True
        mock_run.assert_called_once()

        # Check osascript was called
        call_args = mock_run.call_args
        assert call_args[0][0][0] == "osascript"

    @patch("subprocess.run")
    def test_send_desktop_failure_sound(self, mock_run):
        """Test that failure notifications use different sound."""
        mock_run.return_value = MagicMock(returncode=0)

        config = NotificationConfig(
            desktop_enabled=True,
            desktop_sound_failure="Basso",
        )
        notifier = BQASNotifier(config=config)

        notification = Notification(status="failure", message="Test failed")
        notifier._send_desktop(notification)

        # Check that Basso sound was used
        call_args = mock_run.call_args[0][0]
        assert "Basso" in call_args[2]

    @patch("urllib.request.urlopen")
    def test_send_slack(self, mock_urlopen):
        """Test sending Slack notification."""
        mock_response = MagicMock()
        mock_response.status = 200
        mock_urlopen.return_value.__enter__.return_value = mock_response

        config = NotificationConfig(
            slack_enabled=True,
            slack_webhook_url="https://hooks.slack.com/test",
            slack_channel="#test",
        )
        notifier = BQASNotifier(config=config)

        notification = Notification(
            status="failure",
            message="Tests failed",
            details="INT-005, INT-012",
        )
        result = notifier._send_slack(notification)

        assert result is True
        mock_urlopen.assert_called_once()

    def test_get_title(self):
        """Test title generation based on status."""
        assert BQASNotifier._get_title("success") == "BQAS Erfolgreich"
        assert BQASNotifier._get_title("failure") == "BQAS Fehlgeschlagen"
        assert BQASNotifier._get_title("warning") == "BQAS Warnung"
        assert BQASNotifier._get_title("unknown") == "BQAS"

    def test_get_emoji(self):
        """Test emoji generation for Slack."""
        assert BQASNotifier._get_emoji("success") == ":white_check_mark:"
        assert BQASNotifier._get_emoji("failure") == ":x:"
        assert BQASNotifier._get_emoji("warning") == ":warning:"

    def test_get_color(self):
        """Test color generation for Slack attachments."""
        assert BQASNotifier._get_color("success") == "good"
        assert BQASNotifier._get_color("failure") == "danger"
        assert BQASNotifier._get_color("warning") == "warning"


class TestNotifierIntegration:
    """Integration tests for the notifier system."""

    def test_full_notification_flow(self):
        """Test complete notification flow with logging only."""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as f:
            log_path = f.name

        try:
            config = NotificationConfig(
                enabled=True,
                desktop_enabled=False,  # Disable for CI
                slack_enabled=False,
                email_enabled=False,
                log_file=log_path,
            )
            notifier = BQASNotifier(config=config)

            # Success notification
            success_notif = Notification(
                status="success",
                message="All BQAS tests passed",
                details="Golden: 97/97, RAG: 26/26, Synthetic: 50/50",
            )
            result = notifier.notify(success_notif)
            assert result is True

            # Failure notification
            failure_notif = Notification(
                status="failure",
                message="3 tests failed",
                details="INT-005, INT-012, RAG-003",
            )
            result = notifier.notify(failure_notif)
            assert result is True

            # Check both notifications were logged
            with open(log_path) as f:
                lines = f.readlines()
                assert len(lines) == 2

                first = json.loads(lines[0])
                assert first["status"] == "success"

                second = json.loads(lines[1])
                assert second["status"] == "failure"
        finally:
            os.unlink(log_path)

    def test_notification_with_special_characters(self):
        """Test notifications with special characters in message."""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as f:
            log_path = f.name

        try:
            config = NotificationConfig(
                enabled=True,
                desktop_enabled=False,
                log_file=log_path,
            )
            notifier = BQASNotifier(config=config)

            notification = Notification(
                status="warning",
                message='Test mit "Anführungszeichen" und Umlauten: äöü',
                details="Spezielle Zeichen: <>&'",
            )
            result = notifier.notify(notification)
            assert result is True

            # Verify logged correctly
            with open(log_path) as f:
                log_entry = json.loads(f.read().strip())
                assert "Anführungszeichen" in log_entry["message"]
                assert "äöü" in log_entry["message"]
        finally:
            os.unlink(log_path)


class TestSchedulerScripts:
    """Tests for scheduler shell scripts."""

    def test_run_bqas_script_exists(self):
        """Test that run_bqas.sh exists and is executable."""
        script_path = Path(__file__).parent.parent.parent / "scripts" / "run_bqas.sh"
        assert script_path.exists(), f"Script not found: {script_path}"

        # Check executable
        assert os.access(script_path, os.X_OK), "Script is not executable"

    def test_run_bqas_script_syntax(self):
        """Test run_bqas.sh has valid bash syntax."""
        script_path = Path(__file__).parent.parent.parent / "scripts" / "run_bqas.sh"

        result = subprocess.run(
            ["bash", "-n", str(script_path)],
            capture_output=True,
            text=True,
        )
        assert result.returncode == 0, f"Syntax error: {result.stderr}"

    def test_install_script_exists(self):
        """Test that install_bqas_scheduler.sh exists."""
        script_path = Path(__file__).parent.parent.parent / "scripts" / "install_bqas_scheduler.sh"
        assert script_path.exists(), f"Script not found: {script_path}"
        assert os.access(script_path, os.X_OK), "Script is not executable"

    def test_install_script_syntax(self):
        """Test install_bqas_scheduler.sh has valid bash syntax."""
        script_path = Path(__file__).parent.parent.parent / "scripts" / "install_bqas_scheduler.sh"

        result = subprocess.run(
            ["bash", "-n", str(script_path)],
            capture_output=True,
            text=True,
        )
        assert result.returncode == 0, f"Syntax error: {result.stderr}"

    def test_plist_file_exists(self):
        """Test that launchd plist template exists."""
        plist_path = Path(__file__).parent.parent.parent / "scripts" / "com.breakpilot.bqas.plist"
        assert plist_path.exists(), f"Plist not found: {plist_path}"

    @pytest.mark.skipif(sys.platform != "darwin", reason="plutil only available on macOS")
    def test_plist_valid_xml(self):
        """Test that plist is valid XML."""
        plist_path = Path(__file__).parent.parent.parent / "scripts" / "com.breakpilot.bqas.plist"

        result = subprocess.run(
            ["plutil", "-lint", str(plist_path)],
            capture_output=True,
            text=True,
        )
        assert result.returncode == 0, f"Invalid plist: {result.stderr}"

    def test_git_hook_exists(self):
        """Test that git hook template exists."""
        hook_path = Path(__file__).parent.parent.parent / "scripts" / "post-commit.hook"
        assert hook_path.exists(), f"Hook not found: {hook_path}"

    def test_run_bqas_help(self):
        """Test run_bqas.sh --help flag."""
        script_path = Path(__file__).parent.parent.parent / "scripts" / "run_bqas.sh"

        result = subprocess.run(
            [str(script_path), "--help"],
            capture_output=True,
            text=True,
        )
        assert result.returncode == 0
        assert "Usage" in result.stdout
        assert "--quick" in result.stdout
        assert "--golden" in result.stdout

    def test_install_script_status(self):
        """Test install_bqas_scheduler.sh status command."""
        script_path = Path(__file__).parent.parent.parent / "scripts" / "install_bqas_scheduler.sh"

        result = subprocess.run(
            [str(script_path), "status"],
            capture_output=True,
            text=True,
        )
        # Status should always work (even if not installed)
        assert result.returncode == 0
        assert "BQAS Scheduler Status" in result.stdout
