"""Tests for user persistence, git sync metadata, and DB-first initialization.

Verifies that:
- Users are loaded from DB on init, not from git filesystem
- Git sync metadata (last_sync_at) is persisted and prevents re-discovery
- Domain events are emitted on user mutations
- The API returns git_sync metadata
"""

from __future__ import annotations

import time
from pathlib import Path

import pytest

from proxym.dashboard.users import User, UserManager


@pytest.fixture(autouse=True)
def _reset_user_manager():
    UserManager.reset()
    yield
    UserManager.reset()


class TestDBFirstInit:
    """UserManager should load from DB and not touch git when users already exist."""

    def test_init_loads_from_db_skips_git(self, tmp_path, monkeypatch):
        """If DB has users, init should NOT call git."""
        db = tmp_path / "users.sqlite"

        # First manager: create a user manually
        mgr1 = UserManager(persist_path=db)
        mgr1.create_user(User(name="Alice", email="alice@example.com"))
        assert len(mgr1.list_users()) >= 1

        UserManager.reset()

        # Patch git to prove it's NOT called
        git_called = False
        original = UserManager._read_git_identity

        def _no_git(self):
            nonlocal git_called
            git_called = True
            return "", ""

        monkeypatch.setattr(UserManager, "_read_git_identity", _no_git)

        # Second manager: loads from DB
        mgr2 = UserManager(persist_path=db)
        alice = mgr2.find_by_email("alice@example.com")
        assert alice is not None
        assert alice.name == "Alice"
        assert not git_called, "Git should not be called when DB has users"

    def test_init_discovers_git_when_db_empty(self, tmp_path, monkeypatch):
        """When DB is empty and no sync recorded, git discovery should run."""
        db = tmp_path / "new.sqlite"

        discovered = False
        original = UserManager._discover_git_user

        def _track_discover(self):
            nonlocal discovered
            discovered = True
            return None

        monkeypatch.setattr(UserManager, "_discover_git_user", _track_discover)

        mgr = UserManager(persist_path=db)
        assert discovered, "Git discovery should run when DB is empty"

    def test_init_skips_git_when_sync_metadata_exists(self, tmp_path, monkeypatch):
        """If git_sync_meta has last_sync_at, don't auto-discover even with empty users."""
        db = tmp_path / "synced.sqlite"

        # First manager: set sync metadata without creating users
        mgr1 = UserManager(persist_path=db)
        mgr1.set_git_sync_metadata(time.time(), 0)

        UserManager.reset()

        # Patch git to prove it's NOT called
        git_called = False

        def _no_git(self):
            nonlocal git_called
            git_called = True
            return "", ""

        monkeypatch.setattr(UserManager, "_read_git_identity", _no_git)

        mgr2 = UserManager(persist_path=db)
        assert not git_called, "Git should not be called when sync metadata exists"


class TestGitSyncMetadata:
    """Git sync metadata should be persisted and accessible."""

    def test_metadata_roundtrip(self, tmp_path):
        db = tmp_path / "meta.sqlite"
        mgr = UserManager(persist_path=db)

        assert mgr.get_git_sync_metadata() == {}

        now = time.time()
        mgr.set_git_sync_metadata(now, 3)

        meta = mgr.get_git_sync_metadata()
        assert meta["last_sync_at"] == now
        assert meta["discovered_count"] == 3

        # Survives restart
        UserManager.reset()
        mgr2 = UserManager(persist_path=db)
        meta2 = mgr2.get_git_sync_metadata()
        assert meta2["last_sync_at"] == now
        assert meta2["discovered_count"] == 3

    def test_metadata_updates_on_sync(self, tmp_path, monkeypatch):
        db = tmp_path / "sync.sqlite"
        mgr = UserManager(persist_path=db)

        # Stub out actual git operations
        monkeypatch.setattr(mgr, "sync_from_git_log", lambda *a, **kw: [])

        mgr.set_git_sync_metadata(time.time(), 0)
        meta = mgr.get_git_sync_metadata()
        assert "last_sync_at" in meta


class TestUserPersistence:
    """Users should survive manager restart via SQLite."""

    def test_create_and_reload(self, tmp_path):
        db = tmp_path / "persist.sqlite"

        mgr1 = UserManager(persist_path=db)
        mgr1.create_user(User(name="Bob", email="bob@example.com", role="admin"))
        mgr1.create_user(User(name="Carol", email="carol@example.com"))

        UserManager.reset()
        mgr2 = UserManager(persist_path=db)

        assert mgr2.find_by_email("bob@example.com") is not None
        assert mgr2.find_by_email("carol@example.com") is not None
        bob = mgr2.find_by_email("bob@example.com")
        assert bob.role == "admin"

    def test_update_persists(self, tmp_path):
        db = tmp_path / "update.sqlite"
        mgr = UserManager(persist_path=db)
        user = mgr.create_user(User(name="Dave", email="dave@example.com"))
        mgr.update_user(user.id, {"role": "admin", "is_default": True})

        UserManager.reset()
        mgr2 = UserManager(persist_path=db)
        dave = mgr2.find_by_email("dave@example.com")
        assert dave.role == "admin"
        assert dave.is_default is True

    def test_delete_persists(self, tmp_path):
        db = tmp_path / "delete.sqlite"
        mgr = UserManager(persist_path=db)
        user = mgr.create_user(User(name="Eve", email="eve@example.com"))
        mgr.delete_user(user.id)

        UserManager.reset()
        mgr2 = UserManager(persist_path=db)
        assert mgr2.find_by_email("eve@example.com") is None


class TestDomainEvents:
    """User mutations should emit domain events."""

    def test_user_events_are_defined(self):
        from proxym.domain.events import (
            UserCreated,
            UserUpdated,
            UserDeleted,
            UsersSyncedFromGit,
        )
        # Smoke: events can be constructed
        e1 = UserCreated(aggregate_id="users", data={"user_id": "u1"})
        assert e1.event_type == "UserCreated"

        e2 = UsersSyncedFromGit(aggregate_id="users", data={"discovered_count": 2})
        assert e2.event_type == "UsersSyncedFromGit"

    def test_user_commands_are_defined(self):
        from proxym.domain.commands.user_commands import (
            CreateUserCommand,
            UpdateUserCommand,
            DeleteUserCommand,
            SyncGitUsersCommand,
        )
        cmd = CreateUserCommand(name="Test", email="test@example.com")
        assert cmd.name == "Test"

    def test_user_queries_are_defined(self):
        from proxym.domain.queries.user_queries import (
            ListUsersQuery,
            GetUserQuery,
            GetDefaultUserQuery,
            GetGitSyncStatusQuery,
        )
        q = ListUsersQuery(active_only=True)
        assert q.active_only is True


class TestUsersAPIContract:
    """The /dashboard/users API should return git_sync metadata."""

    def test_list_users_includes_git_sync(self, client):
        resp = client.get("/dashboard/users")
        assert resp.status_code == 200
        data = resp.json()
        assert "git_sync" in data
        assert "users" in data
        assert "count" in data

    def test_sync_git_returns_metadata(self, client):
        resp = client.get("/dashboard/users/sync-git")
        assert resp.status_code == 200
        data = resp.json()
        assert "discovered" in data
        assert "git_sync" in data
        if data["git_sync"]:
            assert "last_sync_at" in data["git_sync"]
