#!/usr/bin/env python3
"""
Tests for pattern_recognizer.py - Design pattern detection.

Test Coverage:
- SingletonDetector (4 tests)
- FactoryDetector (4 tests)
- ObserverDetector (3 tests)
- PatternRecognizer Integration (4 tests)
- Multi-Language Support (3 tests)
"""

import os
import sys
import unittest

# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from skill_seekers.cli.pattern_recognizer import (
    FactoryDetector,
    LanguageAdapter,
    ObserverDetector,
    PatternInstance,
    PatternRecognizer,
    SingletonDetector,
)


class TestSingletonDetector(unittest.TestCase):
    """Tests for Singleton pattern detection"""

    def setUp(self):
        self.detector = SingletonDetector(depth="deep")
        self.recognizer = PatternRecognizer(depth="deep")

    def test_surface_detection_by_name(self):
        """Test surface detection using class name"""
        code = """
class DatabaseSingleton:
    def __init__(self):
        self.connection = None
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        self.assertEqual(len(report.patterns), 1)
        pattern = report.patterns[0]
        self.assertEqual(pattern.pattern_type, "Singleton")
        self.assertGreaterEqual(pattern.confidence, 0.6)
        self.assertIn("Singleton", pattern.class_name)

    def test_deep_detection_with_instance_method(self):
        """Test deep detection with getInstance() method"""
        code = """
class Database:
    def getInstance(self):
        return self._instance

    def __init__(self):
        self._instance = None
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        # May or may not detect based on getInstance alone
        # Checking that analysis completes successfully
        self.assertIsNotNone(report)
        self.assertEqual(report.language, "Python")

    def test_python_singleton_with_new(self):
        """Test Python-specific __new__ singleton pattern"""
        code = """
class Config:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        # Detection may vary based on __new__ method signatures from CodeAnalyzer
        # Main check: analysis completes successfully
        self.assertIsNotNone(report)
        self.assertGreaterEqual(report.total_classes, 1)

    def test_java_singleton_pattern(self):
        """Test Java-style Singleton pattern"""
        code = """
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
"""
        report = self.recognizer.analyze_file("test.java", code, "Java")

        # May detect Singleton based on getInstance method
        # Since CodeAnalyzer uses regex for Java, detection may vary
        self.assertIsNotNone(report)


class TestFactoryDetector(unittest.TestCase):
    """Tests for Factory pattern detection"""

    def setUp(self):
        self.detector = FactoryDetector(depth="deep")
        self.recognizer = PatternRecognizer(depth="deep")

    def test_surface_detection_by_name(self):
        """Test surface detection using class name"""
        code = """
class CarFactory:
    def create_car(self, type):
        pass
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Factory"]
        self.assertGreater(len(patterns), 0)
        pattern = patterns[0]
        # Confidence may be adjusted by deep detection
        self.assertGreaterEqual(pattern.confidence, 0.5)
        self.assertIn("Factory", pattern.class_name)

    def test_factory_method_detection(self):
        """Test detection of create/make methods"""
        code = """
class VehicleFactory:
    def create(self, vehicle_type):
        if vehicle_type == 'car':
            return Car()
        elif vehicle_type == 'truck':
            return Truck()

    def make_vehicle(self, specs):
        return Vehicle(specs)
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Factory"]
        self.assertGreater(len(patterns), 0)
        pattern = patterns[0]
        self.assertIn("create", " ".join(pattern.evidence).lower())

    def test_abstract_factory_multiple_methods(self):
        """Test Abstract Factory with multiple creation methods"""
        code = """
class UIFactory:
    def create_button(self):
        pass

    def create_window(self):
        pass

    def create_menu(self):
        pass
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Factory"]
        self.assertGreater(len(patterns), 0)
        pattern = patterns[0]
        self.assertGreaterEqual(pattern.confidence, 0.5)

    def test_parameterized_factory(self):
        """Test parameterized factory pattern"""
        code = """
class ShapeFactory:
    def create_shape(self, shape_type, *args):
        if shape_type == 'circle':
            return Circle(*args)
        elif shape_type == 'square':
            return Square(*args)
        return None
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Factory"]
        self.assertGreater(len(patterns), 0)


class TestObserverDetector(unittest.TestCase):
    """Tests for Observer pattern detection"""

    def setUp(self):
        self.detector = ObserverDetector(depth="deep")
        self.recognizer = PatternRecognizer(depth="deep")

    def test_observer_triplet_detection(self):
        """Test classic attach/detach/notify triplet"""
        code = """
class Subject:
    def __init__(self):
        self.observers = []

    def attach(self, observer):
        self.observers.append(observer)

    def detach(self, observer):
        self.observers.remove(observer)

    def notify(self):
        for observer in self.observers:
            observer.update()
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Observer"]
        self.assertGreater(len(patterns), 0)
        pattern = patterns[0]
        self.assertGreaterEqual(pattern.confidence, 0.8)
        evidence_str = " ".join(pattern.evidence).lower()
        self.assertTrue(
            "attach" in evidence_str and "detach" in evidence_str and "notify" in evidence_str
        )

    def test_pubsub_pattern(self):
        """Test publish/subscribe variant"""
        code = """
class EventBus:
    def subscribe(self, event, handler):
        pass

    def unsubscribe(self, event, handler):
        pass

    def publish(self, event, data):
        pass
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Observer"]
        self.assertGreater(len(patterns), 0)

    def test_event_emitter_pattern(self):
        """Test EventEmitter-style observer"""
        code = """
class EventEmitter:
    def on(self, event, listener):
        pass

    def off(self, event, listener):
        pass

    def emit(self, event, *args):
        pass
"""
        report = self.recognizer.analyze_file("test.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Observer"]
        self.assertGreater(len(patterns), 0)


class TestPatternRecognizerIntegration(unittest.TestCase):
    """Integration tests for PatternRecognizer"""

    def setUp(self):
        self.recognizer = PatternRecognizer(depth="deep")

    def test_analyze_singleton_code(self):
        """Test end-to-end Singleton analysis"""
        code = """
class ConfigManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def getInstance(self):
        return self._instance
"""
        report = self.recognizer.analyze_file("config.py", code, "Python")

        self.assertEqual(report.file_path, "config.py")
        self.assertEqual(report.language, "Python")
        self.assertGreater(len(report.patterns), 0)
        self.assertGreater(report.total_classes, 0)

    def test_analyze_factory_code(self):
        """Test end-to-end Factory analysis"""
        code = """
class AnimalFactory:
    def create_animal(self, animal_type):
        if animal_type == 'dog':
            return Dog()
        elif animal_type == 'cat':
            return Cat()
        return None
"""
        report = self.recognizer.analyze_file("factory.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Factory"]
        self.assertGreater(len(patterns), 0)

    def test_analyze_observer_code(self):
        """Test end-to-end Observer analysis"""
        code = """
class WeatherStation:
    def __init__(self):
        self.observers = []

    def attach(self, observer):
        self.observers.append(observer)

    def detach(self, observer):
        self.observers.remove(observer)

    def notify(self):
        for obs in self.observers:
            obs.update(self.temperature)
"""
        report = self.recognizer.analyze_file("weather.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Observer"]
        self.assertGreater(len(patterns), 0)

    def test_pattern_report_summary(self):
        """Test PatternReport.get_summary() method"""
        code = """
class LoggerSingleton:
    _instance = None

    def getInstance(self):
        return self._instance

class LoggerFactory:
    def create_logger(self, type):
        return Logger(type)
"""
        report = self.recognizer.analyze_file("logging.py", code, "Python")

        summary = report.get_summary()
        self.assertIsInstance(summary, dict)
        # Summary returns pattern counts by type (e.g., {'Singleton': 1, 'Factory': 1})
        if summary:
            # Check that at least one pattern type is in summary
            total_count = sum(summary.values())
            self.assertGreater(total_count, 0)


class TestMultiLanguageSupport(unittest.TestCase):
    """Tests for multi-language pattern detection"""

    def setUp(self):
        self.recognizer = PatternRecognizer(depth="deep")

    def test_python_patterns(self):
        """Test Python-specific patterns"""
        code = """
class DatabaseConnection:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
"""
        report = self.recognizer.analyze_file("db.py", code, "Python")

        # Detection depends on CodeAnalyzer's ability to parse __new__ method
        # Main check: analysis completes successfully
        self.assertIsNotNone(report)
        self.assertEqual(report.language, "Python")

    def test_javascript_patterns(self):
        """Test JavaScript-specific patterns"""
        code = """
const singleton = (function() {
    let instance;

    function createInstance() {
        return { name: 'Singleton' };
    }

    return {
        getInstance: function() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
"""
        # Note: CodeAnalyzer uses regex for JavaScript, so detection may be limited
        report = self.recognizer.analyze_file("app.js", code, "JavaScript")
        self.assertIsNotNone(report)

    def test_java_patterns(self):
        """Test Java-specific patterns"""
        code = """
public class Logger {
    private static Logger instance;

    private Logger() {}

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
}
"""
        report = self.recognizer.analyze_file("Logger.java", code, "Java")
        self.assertIsNotNone(report)


class TestExtendedPatternDetectors(unittest.TestCase):
    """Tests for extended pattern detectors (Builder, Adapter, Command, etc.)"""

    def setUp(self):
        self.recognizer = PatternRecognizer(depth="deep")

    def test_builder_pattern(self):
        """Test Builder pattern detection"""
        code = """
class QueryBuilder:
    def __init__(self):
        self.query = {}

    def where(self, condition):
        self.query['where'] = condition
        return self

    def orderBy(self, field):
        self.query['order'] = field
        return self

    def build(self):
        return Query(self.query)
"""
        report = self.recognizer.analyze_file("query.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Builder"]
        self.assertGreater(len(patterns), 0)

    def test_adapter_pattern(self):
        """Test Adapter pattern detection"""
        code = """
class DatabaseAdapter:
    def __init__(self, adaptee):
        self.adaptee = adaptee

    def query(self, sql):
        return self.adaptee.execute(sql)

    def connect(self):
        return self.adaptee.open_connection()
"""
        report = self.recognizer.analyze_file("adapter.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Adapter"]
        self.assertGreater(len(patterns), 0)

    def test_command_pattern(self):
        """Test Command pattern detection"""
        code = """
class SaveCommand:
    def __init__(self, receiver):
        self.receiver = receiver

    def execute(self):
        self.receiver.save()

    def undo(self):
        self.receiver.revert()
"""
        report = self.recognizer.analyze_file("command.py", code, "Python")

        patterns = [p for p in report.patterns if p.pattern_type == "Command"]
        self.assertGreater(len(patterns), 0)


class TestLanguageAdapter(unittest.TestCase):
    """Tests for language-specific adaptations"""

    def test_python_decorator_boost(self):
        """Test Python @decorator syntax boost"""
        pattern = PatternInstance(
            pattern_type="Decorator",
            category="Structural",
            confidence=0.6,
            location="test.py",
            class_name="LogDecorator",
            evidence=["Uses @decorator syntax"],
        )

        adapted = LanguageAdapter.adapt_for_language(pattern, "Python")
        self.assertGreater(adapted.confidence, 0.6)
        self.assertIn("Python @decorator", " ".join(adapted.evidence))

    def test_javascript_module_pattern(self):
        """Test JavaScript module pattern boost"""
        pattern = PatternInstance(
            pattern_type="Singleton",
            category="Creational",
            confidence=0.5,
            location="app.js",
            class_name="App",
            evidence=["Has getInstance", "module pattern detected"],
        )

        adapted = LanguageAdapter.adapt_for_language(pattern, "JavaScript")
        self.assertGreater(adapted.confidence, 0.5)

    def test_no_pattern_returns_none(self):
        """Test None input returns None"""
        result = LanguageAdapter.adapt_for_language(None, "Python")
        self.assertIsNone(result)


if __name__ == "__main__":
    # Run tests with verbose output
    unittest.main(verbosity=2)
