"""Tests for learning models, pattern library, and seeds."""

import pytest
import time
from uuid import uuid4

from xyglue.learn.models import Pattern
from xyglue.learn.patterns import PatternLibrary
from xyglue.learn.seeds import (
    seed_patterns, get_seed_patterns, get_seed_count,
    get_seed_categories, get_seeds_by_category,
    SEED_PATTERNS,
)


class TestPattern:
    def test_basic_pattern(self):
        p = Pattern(name="test", signature="timeout:", category="timeout")
        assert p.name == "test"
        assert p.confidence == 0.5
        assert p.is_seeded is False

    def test_record_use_success(self):
        p = Pattern(confidence=0.5)
        p.record_use(True)
        assert p.uses == 1
        assert p.successes == 1
        assert p.confidence == 0.55

    def test_record_use_failure(self):
        p = Pattern(confidence=0.5)
        p.record_use(False)
        assert p.uses == 1
        assert p.failures == 1
        assert p.confidence == 0.4

    def test_confidence_bounds(self):
        p = Pattern(confidence=0.98)
        p.record_use(True)
        assert p.confidence <= 1.0

        p2 = Pattern(confidence=0.05)
        p2.record_use(False)
        assert p2.confidence >= 0.0

    def test_decay(self):
        p = Pattern(confidence=0.8)
        p.decay(10, rate=0.01)
        assert p.confidence == pytest.approx(0.7)

    def test_decay_seeded_half_rate(self):
        p = Pattern(confidence=0.8, is_seeded=True)
        p.decay(10, rate=0.01)
        assert p.confidence == pytest.approx(0.75)

    def test_matches_exact(self):
        p = Pattern(signature="timeout:http:get")
        assert p.matches("timeout:http:get") is True

    def test_matches_prefix(self):
        p = Pattern(signature="timeout:")
        assert p.matches("timeout:http:get") is True

    def test_matches_component_overlap(self):
        p = Pattern(signature="timeout:http")
        assert p.matches("timeout:api") is True  # Both have "timeout"

    def test_no_match_empty(self):
        p = Pattern(signature="")
        assert p.matches("timeout:") is False

    def test_match_score(self):
        p = Pattern(signature="timeout:http:get")
        assert p.match_score("timeout:http:get") == 1.0
        assert p.match_score("network:db:write") < 0.5

    def test_success_rate(self):
        p = Pattern(uses=10, successes=7, failures=3)
        assert p.success_rate == pytest.approx(0.7)

    def test_failure_rate(self):
        p = Pattern(uses=10, successes=7, failures=3)
        assert p.failure_rate == pytest.approx(0.3)

    def test_is_effective(self):
        p = Pattern(confidence=0.6, uses=5, successes=3, failures=2)
        assert p.is_effective is True

    def test_is_proven(self):
        p = Pattern(uses=5, successes=4, failures=1)
        assert p.is_proven is True

    def test_not_proven(self):
        p = Pattern(uses=2, successes=2)
        assert p.is_proven is False  # Not enough uses

    def test_should_evict(self):
        p = Pattern(confidence=0.1, is_seeded=False)
        assert p.should_evict is True

    def test_seeded_not_evicted(self):
        p = Pattern(confidence=0.1, is_seeded=True)
        assert p.should_evict is False

    def test_merge_with(self):
        p1 = Pattern(name="a", confidence=0.6, uses=5, successes=3, failures=2)
        p2 = Pattern(name="b", confidence=0.8, uses=3, successes=2, failures=1,
                      description="better fix", fix_type="retry")
        p1.merge_with(p2)
        assert p1.uses == 8
        assert p1.fix_type == "retry"  # Higher confidence fix wins
        assert p1.confidence == pytest.approx(0.7)

    def test_similarity(self):
        p1 = Pattern(signature="timeout:", category="timeout", fix_type="retry")
        p2 = Pattern(signature="timeout:", category="timeout", fix_type="retry")
        assert p1.similarity(p2) >= 0.8

    def test_to_dict_from_dict(self):
        p = Pattern(
            name="test", signature="timeout:", category="timeout",
            fix_type="retry", confidence=0.75, tags=["t1"],
        )
        d = p.to_dict()
        p2 = Pattern.from_dict(d)
        assert p2.name == "test"
        assert p2.confidence == 0.75
        assert p2.tags == ["t1"]


class TestPatternLibrary:
    def test_add_and_get(self):
        lib = PatternLibrary()
        p = Pattern(name="test", signature="timeout:")
        lib.add(p)
        assert lib.get(p.id) is p
        assert lib.count == 1

    def test_remove(self):
        lib = PatternLibrary()
        p = Pattern(name="test")
        lib.add(p)
        assert lib.remove(p.id) is True
        assert lib.count == 0

    def test_get_by_name(self):
        lib = PatternLibrary()
        p = Pattern(name="my_pattern")
        lib.add(p)
        found = lib.get_by_name("my_pattern")
        assert found is p

    def test_find_match(self):
        lib = PatternLibrary()
        p = Pattern(name="timeout", signature="timeout:", confidence=0.8)
        lib.add(p)
        match = lib.find_match("timeout:http:get")
        assert match is p

    def test_find_match_below_threshold(self):
        lib = PatternLibrary()
        p = Pattern(name="low", signature="timeout:", confidence=0.3)
        lib.add(p)
        match = lib.find_match("timeout:", min_confidence=0.5)
        assert match is None

    def test_find_all_matches(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a", signature="timeout:", confidence=0.8))
        lib.add(Pattern(name="b", signature="timeout:", confidence=0.6))
        matches = lib.find_all_matches("timeout:", min_confidence=0.3)
        assert len(matches) == 2
        assert matches[0].confidence >= matches[1].confidence

    def test_record_use_evicts_low(self):
        lib = PatternLibrary(min_confidence=0.5)
        p = Pattern(name="weak", signature="x:", confidence=0.5, is_seeded=False)
        lib.add(p)
        lib.record_use(p.id, success=False)  # Drops to 0.4
        assert lib.get(p.id) is None  # Evicted

    def test_capacity_limit(self):
        lib = PatternLibrary(max_patterns=3)
        for i in range(5):
            lib.add(Pattern(name=f"p{i}", confidence=0.5 + i * 0.1))
        assert lib.count <= 3

    def test_get_by_category(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a", category="timeout"))
        lib.add(Pattern(name="b", category="network"))
        lib.add(Pattern(name="c", category="timeout"))
        assert len(lib.get_by_category("timeout")) == 2

    def test_get_categories(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a", category="timeout"))
        lib.add(Pattern(name="b", category="network"))
        cats = lib.get_categories()
        assert "timeout" in cats
        assert "network" in cats

    def test_get_by_fix_type(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a", fix_type="retry"))
        lib.add(Pattern(name="b", fix_type="skip"))
        assert len(lib.get_by_fix_type("retry")) == 1

    def test_export_import(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a", signature="timeout:", confidence=0.8))
        lib.add(Pattern(name="b", signature="network:", confidence=0.7))
        exported = lib.export_all()
        assert len(exported) == 2

        lib2 = PatternLibrary()
        count = lib2.import_all(exported)
        assert count == 2
        assert lib2.count == 2

    def test_merge_from(self):
        lib1 = PatternLibrary()
        lib1.add(Pattern(name="a", signature="timeout:", category="timeout", confidence=0.7))
        lib2 = PatternLibrary()
        lib2.add(Pattern(name="b", signature="network:", category="network", confidence=0.8))
        added = lib1.merge_from(lib2)
        assert added == 1
        assert lib1.count == 2

    def test_add_batch(self):
        lib = PatternLibrary()
        patterns = [Pattern(name=f"p{i}") for i in range(5)]
        count = lib.add_batch(patterns)
        assert count == 5

    def test_get_stats(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a", is_seeded=True, confidence=0.8, uses=5, successes=4, failures=1))
        lib.add(Pattern(name="b", is_seeded=False, confidence=0.6, uses=3, successes=2, failures=1))
        stats = lib.get_stats()
        assert stats["total_patterns"] == 2
        assert stats["seeded"] == 1
        assert stats["learned"] == 1
        assert stats["total_uses"] == 8

    def test_get_top_patterns(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="low", confidence=0.3))
        lib.add(Pattern(name="high", confidence=0.9))
        lib.add(Pattern(name="mid", confidence=0.6))
        top = lib.get_top_patterns(limit=2)
        assert len(top) == 2
        assert top[0].confidence >= top[1].confidence

    def test_clear(self):
        lib = PatternLibrary()
        lib.add(Pattern(name="a"))
        lib.clear()
        assert lib.count == 0


class TestSeeds:
    def test_seed_count(self):
        assert get_seed_count() == 20

    def test_seed_patterns_function(self):
        lib = PatternLibrary()
        seed_patterns(lib)
        assert lib.count == 20

    def test_get_seed_patterns(self):
        patterns = get_seed_patterns()
        assert len(patterns) == 20
        assert all(p.is_seeded for p in patterns)
        assert all(p.uses >= 1 for p in patterns)

    def test_seed_categories(self):
        cats = get_seed_categories()
        assert "timeout" in cats
        assert "network" in cats
        assert "validation" in cats

    def test_seeds_by_category(self):
        timeout_seeds = get_seeds_by_category("timeout")
        assert len(timeout_seeds) >= 1

    def test_all_seeds_have_required_fields(self):
        for data in SEED_PATTERNS:
            assert "name" in data
            assert "signature" in data
            assert "category" in data
            assert "fix_type" in data
            assert "confidence" in data

    def test_seeded_library_match(self, seeded_library):
        match = seeded_library.find_match("timeout:http:get", min_confidence=0.5)
        assert match is not None
        assert match.fix_type == "retry"
