"""
Tests for projects.py functions.

Tests the lazy initialization exclusion system.
"""

from __future__ import annotations

import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest
from returns.result import Failure, Success

from lattice.core.types.evidence import ExcludedProject
from lattice.shell.projects import (
    exclude_project,
    find_project_by_path,
    include_project,
    is_excluded,
    load_excluded,
    load_projects,
    save_excluded,
    save_projects,
)


@pytest.fixture
def temp_config_dir():
    """Create a temporary config directory for testing."""
    with tempfile.TemporaryDirectory() as tmpdir:
        config_dir = Path(tmpdir)
        # Ensure clean state
        projects_file = config_dir / "projects.toml"
        if projects_file.exists():
            projects_file.unlink()

        with (
            patch("lattice.shell.projects.get_projects_dir", return_value=config_dir),
            patch(
                "lattice.shell.projects.resolve_global_dir",
                return_value=Success(config_dir),
            ),
            patch(
                "lattice.shell.config.resolve_global_dir",
                return_value=Success(config_dir),
            ),
        ):
            yield config_dir


@pytest.fixture
def temp_project_dir():
    """Create a temporary project directory."""
    with tempfile.TemporaryDirectory() as tmpdir:
        project_path = Path(tmpdir) / "my-project"
        project_path.mkdir()
        yield project_path


class TestExcludedProjects:
    """Tests for excluded project management."""

    def test_load_excluded_empty(self, temp_config_dir):
        """Load excluded returns empty list when file doesn't exist."""
        result = load_excluded()
        assert isinstance(result, Success)
        assert result.unwrap() == []

    def test_save_and_load_excluded(self, temp_config_dir, temp_project_dir):
        """Save and load excluded projects."""
        excluded = [
            ExcludedProject(
                path=str(temp_project_dir),
                excluded_at="2026-02-21T10:00:00Z",
                reason="Test exclusion",
            )
        ]

        save_result = save_excluded(excluded)
        assert isinstance(save_result, Success)

        load_result = load_excluded()
        assert isinstance(load_result, Success)
        loaded = load_result.unwrap()
        assert len(loaded) == 1
        assert loaded[0].path == str(temp_project_dir)
        assert loaded[0].reason == "Test exclusion"

    def test_save_excluded_preserves_projects(self, temp_config_dir, temp_project_dir):
        """Saving excluded should preserve existing projects section."""
        from lattice.core.types.evidence import ProjectRegistration

        # First save some projects
        projects = [
            ProjectRegistration(
                path=str(temp_project_dir),
                name="test-project",
                registered_at="2026-02-21T10:00:00Z",
            )
        ]
        save_projects(projects)

        # Now save excluded
        excluded = [
            ExcludedProject(
                path="/tmp/excluded-path",
                excluded_at="2026-02-21T11:00:00Z",
                reason="Read-only fixtures",
            )
        ]
        save_result = save_excluded(excluded)
        assert isinstance(save_result, Success)

        # Verify projects still exist
        loaded_projects = load_projects()
        assert isinstance(loaded_projects, Success)
        assert len(loaded_projects.unwrap()) == 1

        # Verify excluded also exists
        loaded_excluded = load_excluded()
        assert isinstance(loaded_excluded, Success)
        assert len(loaded_excluded.unwrap()) == 1

    def test_is_excluded_true(self, temp_config_dir, temp_project_dir):
        """is_excluded returns True for excluded path."""
        excluded = [
            ExcludedProject(
                path=str(temp_project_dir.resolve()),
                excluded_at="2026-02-21T10:00:00Z",
                reason="Test",
            )
        ]
        save_excluded(excluded)

        result = is_excluded(str(temp_project_dir))
        assert isinstance(result, Success)
        assert result.unwrap() is True

    def test_is_excluded_false(self, temp_config_dir):
        """is_excluded returns False for non-excluded path."""
        result = is_excluded("/nonexistent/path")
        assert isinstance(result, Success)
        assert result.unwrap() is False


class TestExcludeProject:
    """Tests for exclude_project function."""

    def test_exclude_nonexistent_path(self, temp_config_dir):
        """Excluding nonexistent path returns Failure."""
        result = exclude_project("/nonexistent/path/12345")
        assert isinstance(result, Failure)
        assert "does not exist" in result.failure().lower()

    def test_exclude_project_success(self, temp_config_dir, temp_project_dir):
        """Excluding a project adds it to excluded list."""
        result = exclude_project(str(temp_project_dir), "Test reason")
        assert isinstance(result, Success)
        excluded = result.unwrap()
        assert excluded.path == str(temp_project_dir.resolve())
        assert excluded.reason == "Test reason"

        # Verify it's in the list
        loaded = load_excluded()
        assert isinstance(loaded, Success)
        assert len(loaded.unwrap()) == 1

    def test_exclude_already_excluded(self, temp_config_dir, temp_project_dir):
        """Excluding an already-excluded project returns Failure."""
        exclude_project(str(temp_project_dir))

        result = exclude_project(str(temp_project_dir))
        assert isinstance(result, Failure)
        assert "already excluded" in result.failure().lower()


class TestIncludeProject:
    """Tests for include_project function."""

    def test_include_nonexistent_path(self, temp_config_dir):
        """Including a path not in excluded list returns Failure."""
        result = include_project("/nonexistent/path")
        assert isinstance(result, Failure)
        assert "not in excluded list" in result.failure().lower()

    def test_include_success(self, temp_config_dir, temp_project_dir):
        """Including removes from excluded list."""
        # First exclude
        exclude_project(str(temp_project_dir))

        # Now include
        result = include_project(str(temp_project_dir))
        assert isinstance(result, Success)

        # Verify removed from list
        loaded = load_excluded()
        assert isinstance(loaded, Success)
        assert len(loaded.unwrap()) == 0


class TestFindProjectByPath:
    """Tests for find_project_by_path function."""

    def test_find_unregistered_project(self, temp_config_dir):
        """Finding unregistered path returns Failure."""
        result = find_project_by_path("/nonexistent/path")
        assert isinstance(result, Failure)


class TestGenerateUniqueProjectName:
    """Tests for generate_unique_project_name (Phase 2: lazy-init)."""

    def test_no_collision(self):
        """Returns base_name when no collision."""
        from lattice.shell.projects import generate_unique_project_name

        assert generate_unique_project_name("myapp", "parent", []) == "myapp"
        assert generate_unique_project_name("myapp", "parent", ["other"]) == "myapp"

    def test_single_collision(self):
        """Appends parent dir on collision."""
        from lattice.shell.projects import generate_unique_project_name

        result = generate_unique_project_name("myapp", "work", ["myapp"])
        assert result == "work-myapp"

    def test_double_collision_single_pass(self):
        """Double collision returns disambiguated name (single-pass, known limitation)."""
        from lattice.shell.projects import generate_unique_project_name

        # Both "myapp" and "work-myapp" are taken — returns "work-myapp" regardless
        result = generate_unique_project_name("myapp", "work", ["myapp", "work-myapp"])
        assert result == "work-myapp"


class TestLoadConfigWithFallback:
    """Tests for load_config_with_fallback priority chain (Phase 1/2)."""

    def test_project_config_takes_priority(self, tmp_path):
        """Project config with model wins over global."""
        from lattice.shell.config import load_config_with_fallback

        p = tmp_path / "config.toml"
        p.write_text('[compiler]\nmodel = "project-model"')
        config, path_used = load_config_with_fallback(p)
        assert config.compiler.model == "project-model"
        assert path_used == str(p)

    def test_none_path_falls_to_default(self):
        """None project path uses global or default."""
        from lattice.shell.config import load_config_with_fallback

        config, _ = load_config_with_fallback(None)
        # Should not raise; config is always returned
        assert config is not None

    def test_missing_project_config_falls_to_default(self, tmp_path):
        """Non-existent project config falls back gracefully."""
        from lattice.shell.config import load_config_with_fallback

        missing = tmp_path / "nonexistent.toml"
        config, path_used = load_config_with_fallback(missing)
        # path_used may be global config path or None (default)
        assert config is not None
