"""
Tests for kodara.cloud.client and kodara.cloud.auth.

All tests use mocks — no real network calls are made.
"""

from __future__ import annotations

import json
import time
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest


# ── Helpers ────────────────────────────────────────────────────────────────────


def _write_json(path: Path, data: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data), encoding="utf-8")


# ── client.py ──────────────────────────────────────────────────────────────────


class TestGetConfigPath:
    def test_returns_kodara_config_json(self):
        from kodara.cloud.client import get_config_path

        p = get_config_path()
        assert p.name == "config.json"
        assert p.parent.name == ".kodara"
        assert p.parent.parent == Path.home()


class TestGetAuthPath:
    def test_returns_kodara_auth_json(self):
        from kodara.cloud.client import get_auth_path

        p = get_auth_path()
        assert p.name == "auth.json"
        assert p.parent.name == ".kodara"
        assert p.parent.parent == Path.home()


class TestGetClient:
    def test_import_error_when_supabase_missing(self):
        """get_client() must raise ImportError when supabase is not installed."""
        import sys
        import importlib

        # Force supabase to appear uninstallable
        with patch.dict(sys.modules, {"supabase": None}):
            # Reload the module so the patched sys.modules takes effect
            import kodara.cloud.client as cc
            import importlib
            importlib.reload(cc)
            with pytest.raises(ImportError, match="supabase"):
                cc.get_client()
            importlib.reload(cc)  # restore

    def test_returns_client_when_supabase_available(self, tmp_path, monkeypatch):
        """get_client() returns a client object when supabase is available."""
        mock_client = MagicMock()
        mock_create = MagicMock(return_value=mock_client)

        monkeypatch.setattr(
            "kodara.cloud.client.get_config_path",
            lambda: tmp_path / "config.json",
        )
        monkeypatch.setattr(
            "kodara.cloud.client.get_auth_path",
            lambda: tmp_path / "auth.json",
        )

        with patch.dict("sys.modules", {"supabase": MagicMock(create_client=mock_create)}):
            import importlib
            import kodara.cloud.client as cc
            importlib.reload(cc)

            monkeypatch.setattr(cc, "get_config_path", lambda: tmp_path / "config.json")
            monkeypatch.setattr(cc, "get_auth_path", lambda: tmp_path / "auth.json")

            with patch("kodara.cloud.client.create_client", mock_create, create=True):
                # Patch inside the already-imported module namespace
                cc_create = MagicMock(return_value=mock_client)
                original = None
                try:
                    import supabase as _sb
                    original = _sb.create_client
                    _sb.create_client = cc_create
                except ImportError:
                    pass

                try:
                    # If supabase is actually installed, test that the client is returned
                    result = cc.get_client()
                    assert result is not None
                except ImportError:
                    pytest.skip("supabase not installed — ImportError path tested elsewhere")
                finally:
                    if original is not None:
                        import supabase as _sb
                        _sb.create_client = original

            importlib.reload(cc)


class TestSaveLoadSession:
    def test_round_trip_dict(self, tmp_path, monkeypatch):
        """save_session then load_session returns the same data."""
        import kodara.cloud.client as cc

        monkeypatch.setattr(cc, "get_auth_path", lambda: tmp_path / "auth.json")

        data = {
            "access_token": "tok_abc",
            "refresh_token": "ref_xyz",
            "email": "test@example.com",
            "expires_at": int(time.time()) + 3600,
        }
        cc.save_session(data)
        loaded = cc.load_session()
        assert loaded == data

    def test_save_session_object(self, tmp_path, monkeypatch):
        """save_session works with object-style session (Supabase SDK v2)."""
        import kodara.cloud.client as cc

        monkeypatch.setattr(cc, "get_auth_path", lambda: tmp_path / "auth.json")

        session = MagicMock()
        session.access_token = "tok_obj"
        session.refresh_token = "ref_obj"
        session.expires_at = int(time.time()) + 7200
        session.user = MagicMock()
        session.user.email = "obj@example.com"

        cc.save_session(session)
        loaded = cc.load_session()
        assert loaded["access_token"] == "tok_obj"
        assert loaded["email"] == "obj@example.com"


class TestClearSession:
    def test_deletes_auth_json(self, tmp_path, monkeypatch):
        import kodara.cloud.client as cc

        auth_file = tmp_path / "auth.json"
        _write_json(auth_file, {"access_token": "x"})
        monkeypatch.setattr(cc, "get_auth_path", lambda: auth_file)

        assert auth_file.exists()
        cc.clear_session()
        assert not auth_file.exists()

    def test_no_error_if_missing(self, tmp_path, monkeypatch):
        import kodara.cloud.client as cc

        monkeypatch.setattr(cc, "get_auth_path", lambda: tmp_path / "auth.json")
        cc.clear_session()  # should not raise


class TestIsAuthenticated:
    def test_false_when_no_auth_json(self, tmp_path, monkeypatch):
        import kodara.cloud.client as cc

        monkeypatch.setattr(cc, "get_auth_path", lambda: tmp_path / "auth.json")
        assert cc.is_authenticated() is False

    def test_false_when_session_expired(self, tmp_path, monkeypatch):
        import kodara.cloud.client as cc

        auth_file = tmp_path / "auth.json"
        _write_json(auth_file, {
            "access_token": "tok",
            "refresh_token": "ref",
            "email": "x@y.com",
            "expires_at": int(time.time()) - 100,  # expired
        })
        monkeypatch.setattr(cc, "get_auth_path", lambda: auth_file)
        assert cc.is_authenticated() is False

    def test_true_when_session_valid(self, tmp_path, monkeypatch):
        import kodara.cloud.client as cc

        auth_file = tmp_path / "auth.json"
        _write_json(auth_file, {
            "access_token": "tok",
            "refresh_token": "ref",
            "email": "x@y.com",
            "expires_at": int(time.time()) + 3600,  # valid
        })
        monkeypatch.setattr(cc, "get_auth_path", lambda: auth_file)
        assert cc.is_authenticated() is True


# ── auth.py ────────────────────────────────────────────────────────────────────


class TestRequestOtp:
    def test_calls_sign_in_with_otp(self, tmp_path, monkeypatch):
        """request_otp must call client.auth.sign_in_with_otp with the email dict."""
        mock_client = MagicMock()
        import kodara.cloud.auth as ca

        monkeypatch.setattr(ca, "get_client", lambda: mock_client)

        ca.request_otp("user@example.com")
        mock_client.auth.sign_in_with_otp.assert_called_once_with(
            {"email": "user@example.com"}
        )


class TestVerifyOtp:
    def test_calls_verify_otp_and_saves_session(self, tmp_path, monkeypatch):
        """verify_otp must call verify_otp on client and persist the session."""
        mock_client = MagicMock()
        mock_user = MagicMock()
        mock_user.email = "user@example.com"
        mock_user.id = "uid-123"

        mock_session = MagicMock()
        mock_session.access_token = "at"
        mock_session.refresh_token = "rt"
        mock_session.expires_at = int(time.time()) + 3600
        mock_session.user = mock_user

        mock_response = MagicMock()
        mock_response.session = mock_session
        mock_response.user = mock_user

        mock_client.auth.verify_otp.return_value = mock_response

        saved = {}

        import kodara.cloud.auth as ca

        monkeypatch.setattr(ca, "get_client", lambda: mock_client)
        monkeypatch.setattr(ca, "save_session", lambda s: saved.update({"called": True, "session": s}))

        result = ca.verify_otp("user@example.com", "123456")

        mock_client.auth.verify_otp.assert_called_once_with(
            {"email": "user@example.com", "token": "123456", "type": "email"}
        )
        assert saved.get("called") is True
        assert result["email"] == "user@example.com"


class TestLogout:
    def test_calls_clear_session(self, monkeypatch):
        """logout must always call clear_session regardless of remote sign-out result."""
        mock_client = MagicMock()
        cleared = {}

        import kodara.cloud.auth as ca

        monkeypatch.setattr(ca, "get_client", lambda: mock_client)
        monkeypatch.setattr(ca, "clear_session", lambda: cleared.update({"called": True}))

        ca.logout()
        assert cleared.get("called") is True

    def test_clear_session_called_even_if_signout_fails(self, monkeypatch):
        """clear_session is called even when client.auth.sign_out raises."""
        mock_client = MagicMock()
        mock_client.auth.sign_out.side_effect = Exception("network error")
        cleared = {}

        import kodara.cloud.auth as ca

        monkeypatch.setattr(ca, "get_client", lambda: mock_client)
        monkeypatch.setattr(ca, "clear_session", lambda: cleared.update({"called": True}))

        ca.logout()
        assert cleared.get("called") is True


class TestGetCurrentUser:
    def test_returns_none_when_not_logged_in(self, monkeypatch):
        import kodara.cloud.auth as ca

        monkeypatch.setattr(ca, "is_authenticated", lambda: False)

        result = ca.get_current_user()
        assert result is None

    def test_returns_user_dict_when_logged_in(self, monkeypatch):
        import kodara.cloud.auth as ca

        monkeypatch.setattr(ca, "is_authenticated", lambda: True)
        monkeypatch.setattr(ca, "load_session", lambda: {
            "access_token": "tok",
            "email": "me@example.com",
            "expires_at": int(time.time()) + 3600,
        })

        result = ca.get_current_user()
        assert result is not None
        assert result["email"] == "me@example.com"


# ── Regression: existing imports still work ────────────────────────────────────


class TestRegressionExistingImports:
    def test_memory_store_importable(self):
        from kodara.memory.store import MemoryStore, KodaraIndex  # noqa: F401

    def test_scanner_importable(self):
        from kodara.indexer.scanner import RepositoryScanner  # noqa: F401

    def test_engine_client_importable(self):
        from kodara.engine_client import query_context, query_impact  # noqa: F401

    def test_dependency_graph_importable(self):
        from kodara.graph.dependency import DependencyGraph  # noqa: F401

    def test_cloud_client_importable(self):
        from kodara.cloud import client  # noqa: F401

    def test_cloud_auth_importable(self):
        from kodara.cloud import auth  # noqa: F401
