"""Tests for vr context commands."""

import json
import pytest
from pathlib import Path
from unittest.mock import patch, Mock

from typer.testing import CliRunner

from vr_cli.cli import app
from vr_cli.commands.context import (
    list_contexts,
    show_current,
    switch_context,
    create_new_context,
    delete_existing_context,
    set_custom_url,
)


runner = CliRunner()


class TestContextListCommand:
    """Tests for context list command."""

    def test_list_contexts_default(self):
        """Should list available contexts including default."""
        with patch("vr_cli.commands.context.get_current_context") as mock_current, \
             patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.console") as mock_console:

            mock_current.return_value = "default"
            mock_all.return_value = {
                "default": {
                    "api_url": "https://api.voicerun.com",
                    "frontend_url": "https://app.voicerun.com"
                }
            }
            mock_urls.return_value = (
                "https://api.voicerun.com",
                "https://app.voicerun.com"
            )

            result = runner.invoke(app, ["context", "list"])

        mock_console.print.assert_called()

    def test_list_contexts_with_user_defined(self):
        """Should list user-defined contexts."""
        with patch("vr_cli.commands.context.get_current_context") as mock_current, \
             patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.console") as mock_console:

            mock_current.return_value = "staging"
            mock_all.return_value = {
                "default": {
                    "api_url": "https://api.voicerun.com",
                    "frontend_url": "https://app.voicerun.com"
                },
                "staging": {
                    "api_url": "https://api.staging.example.com",
                    "frontend_url": "https://app.staging.example.com"
                }
            }
            mock_urls.return_value = (
                "https://api.staging.example.com",
                "https://app.staging.example.com"
            )

            result = runner.invoke(app, ["context", "list"])

        mock_console.print.assert_called()


class TestContextCurrentCommand:
    """Tests for context current command."""

    def test_show_current_context_signed_in(self):
        """Should display context info and signed-in user."""
        mock_user = Mock()
        mock_user.name = "Test User"
        mock_user.email = "test@example.com"
        mock_user.id = "user-123"
        mock_user.organization_id = "org-from-session"
        mock_user.is_admin = False

        with patch("vr_cli.commands.context.get_current_context") as mock_current, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.get_organization_id") as mock_org, \
             patch("vr_cli.commands.context.get_current_user") as mock_get_user, \
             patch("vr_cli.commands.context.console") as mock_console:

            mock_current.return_value = "default"
            mock_urls.return_value = (
                "https://api.voicerun.com",
                "https://app.voicerun.com"
            )
            mock_org.return_value = None
            mock_get_user.return_value = mock_user

            result = runner.invoke(app, ["context", "current"])

        # Context + API URL + Frontend URL + Org ID + blank + Signed in as
        assert mock_console.print.call_count >= 6

    def test_show_current_context_admin_with_effective_org(self):
        """Should show Admin: true and Effective Organization ID for admins."""
        mock_user = Mock()
        mock_user.name = "Admin User"
        mock_user.email = "admin@example.com"
        mock_user.id = "user-456"
        mock_user.organization_id = "org-123"
        mock_user.is_admin = True

        with patch("vr_cli.commands.context.get_current_context") as mock_current, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.get_organization_id") as mock_org, \
             patch("vr_cli.commands.context.get_current_user") as mock_get_user, \
             patch("vr_cli.commands.context.console") as mock_console:

            mock_current.return_value = "default"
            mock_urls.return_value = (
                "https://api.voicerun.com",
                "https://app.voicerun.com"
            )
            mock_org.return_value = "org-override"
            mock_get_user.return_value = mock_user

            result = runner.invoke(app, ["context", "current"])

        # Context + API URL + Frontend URL + blank + Signed in as + Org ID + Admin + Effective Org ID
        assert mock_console.print.call_count >= 8

    def test_show_current_context_not_signed_in(self):
        """Should show not signed in when no session."""
        with patch("vr_cli.commands.context.get_current_context") as mock_current, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.get_organization_id") as mock_org, \
             patch("vr_cli.commands.context.get_current_user") as mock_get_user, \
             patch("vr_cli.commands.context.console") as mock_console:

            mock_current.return_value = "default"
            mock_urls.return_value = (
                "https://api.voicerun.com",
                "https://app.voicerun.com"
            )
            mock_org.return_value = None
            mock_get_user.return_value = None

            result = runner.invoke(app, ["context", "current"])

        # Should show "Not signed in"
        assert mock_console.print.call_count >= 5


class TestContextSwitchCommand:
    """Tests for context switch command."""

    def test_switch_to_existing_context(self):
        """Should switch to existing context."""
        with patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.set_current_context") as mock_set, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.console"):

            mock_all.return_value = {
                "default": {"api_url": "https://api.voicerun.com", "frontend_url": "https://app.voicerun.com"},
                "staging": {"api_url": "https://api.staging.com", "frontend_url": "https://app.staging.com"}
            }
            mock_urls.return_value = ("https://api.staging.com", "https://app.staging.com")

            result = runner.invoke(app, ["context", "switch", "staging"])

        assert result.exit_code == 0
        mock_set.assert_called_once_with("staging")

    def test_switch_to_nonexistent_context(self):
        """Should fail when switching to unknown context."""
        with patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.console"):

            mock_all.return_value = {
                "default": {"api_url": "https://api.voicerun.com", "frontend_url": "https://app.voicerun.com"}
            }

            result = runner.invoke(app, ["context", "switch", "nonexistent"])

        assert result.exit_code != 0


class TestContextCreateCommand:
    """Tests for context create command."""

    def test_create_new_context(self):
        """Should create new user-defined context."""
        with patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.create_context") as mock_create, \
             patch("vr_cli.commands.context.console"):

            mock_all.return_value = {"default": {"api_url": "x", "frontend_url": "y"}}

            result = runner.invoke(app, [
                "context", "create",
                "local",
                "http://localhost:8080",
                "http://localhost:3000"
            ])

        assert result.exit_code == 0
        mock_create.assert_called_once_with(
            "local",
            "http://localhost:8080",
            "http://localhost:3000"
        )

    def test_create_existing_context_fails(self):
        """Should fail when context already exists."""
        with patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.console"):

            mock_all.return_value = {
                "default": {"api_url": "x", "frontend_url": "y"},
                "existing": {"api_url": "a", "frontend_url": "b"}
            }

            result = runner.invoke(app, [
                "context", "create",
                "existing",
                "http://api.example.com",
                "http://app.example.com"
            ])

        assert result.exit_code != 0

    def test_create_invalid_api_url(self):
        """Should fail when API URL doesn't start with http(s)://."""
        with patch("vr_cli.commands.context.console"):
            result = runner.invoke(app, [
                "context", "create",
                "invalid",
                "ftp://api.example.com",
                "https://app.example.com"
            ])

        assert result.exit_code != 0

    def test_create_invalid_frontend_url(self):
        """Should fail when frontend URL doesn't start with http(s)://."""
        with patch("vr_cli.commands.context.console"):
            result = runner.invoke(app, [
                "context", "create",
                "invalid",
                "https://api.example.com",
                "ftp://app.example.com"
            ])

        assert result.exit_code != 0


class TestContextDeleteCommand:
    """Tests for context delete command."""

    def test_delete_user_context(self):
        """Should delete user-defined context."""
        with patch("vr_cli.commands.context.delete_context") as mock_delete, \
             patch("vr_cli.commands.context.console"):

            mock_delete.return_value = (True, "Context 'staging' deleted successfully")

            result = runner.invoke(app, ["context", "delete", "staging"])

        assert result.exit_code == 0
        mock_delete.assert_called_once_with("staging")

    def test_delete_predefined_context_fails(self):
        """Should fail when trying to delete predefined context."""
        with patch("vr_cli.commands.context.delete_context") as mock_delete, \
             patch("vr_cli.commands.context.console"):

            mock_delete.return_value = (False, "Cannot delete predefined context 'default'")

            result = runner.invoke(app, ["context", "delete", "default"])

        assert result.exit_code != 0

    def test_delete_nonexistent_context(self):
        """Should fail when context doesn't exist."""
        with patch("vr_cli.commands.context.delete_context") as mock_delete, \
             patch("vr_cli.commands.context.console"):

            mock_delete.return_value = (False, "Context 'nonexistent' not found")

            result = runner.invoke(app, ["context", "delete", "nonexistent"])

        assert result.exit_code != 0


class TestContextSetUrlCommand:
    """Tests for context set-url command."""

    def test_set_custom_url(self):
        """Should set custom API URL."""
        with patch("vr_cli.commands.context.set_current_context") as mock_set, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.console"):

            mock_urls.return_value = (
                "http://localhost:8080",
                "http://localhost:3000"
            )

            result = runner.invoke(app, ["context", "set-url", "http://localhost:8080"])

        assert result.exit_code == 0
        mock_set.assert_called_once_with("custom", "http://localhost:8080")

    def test_set_url_invalid_format(self):
        """Should fail for invalid URL format."""
        with patch("vr_cli.commands.context.console"):
            result = runner.invoke(app, ["context", "set-url", "not-a-url"])

        assert result.exit_code != 0

    def test_set_url_https(self):
        """Should accept HTTPS URLs."""
        with patch("vr_cli.commands.context.set_current_context") as mock_set, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.console"):

            mock_urls.return_value = (
                "https://api.custom.com",
                "https://app.custom.com"
            )

            result = runner.invoke(app, ["context", "set-url", "https://api.custom.com"])

        assert result.exit_code == 0


class TestEnsureVoicerunDir:
    """Tests for ensure_voicerun_dir function."""

    def test_creates_directory_if_not_exists(self, temp_dir):
        """Should create ~/.voicerun directory if it doesn't exist."""
        voicerun_dir = temp_dir / ".voicerun"
        assert not voicerun_dir.exists()

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir):
            from vr_cli.utils.config import ensure_voicerun_dir
            ensure_voicerun_dir()

        assert voicerun_dir.exists()
        assert voicerun_dir.is_dir()

    def test_does_not_fail_if_exists(self, temp_dir):
        """Should not fail if directory already exists."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True)
        assert voicerun_dir.exists()

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir):
            from vr_cli.utils.config import ensure_voicerun_dir
            ensure_voicerun_dir()

        assert voicerun_dir.exists()


class TestContextConfigFunctions:
    """Tests for context configuration functions."""

    def test_get_current_context_default(self, temp_dir, monkeypatch):
        """Should return default when no config file exists."""
        voicerun_dir = temp_dir / ".voicerun"
        config_file = voicerun_dir / "config.json"

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import get_current_context
            result = get_current_context()

        assert result == "default"

    def test_get_current_context_from_file(self, temp_dir, monkeypatch):
        """Should read current context from config file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({"current_context": "staging"}))

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import get_current_context
            result = get_current_context()

        assert result == "staging"

    def test_set_current_context(self, temp_dir):
        """Should save current context to file."""
        voicerun_dir = temp_dir / ".voicerun"
        config_file = voicerun_dir / "config.json"

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import set_current_context
            set_current_context("staging")

        data = json.loads(config_file.read_text())
        assert data["current_context"] == "staging"

    def test_set_current_context_with_custom_url(self, temp_dir):
        """Should save custom API URL when provided."""
        voicerun_dir = temp_dir / ".voicerun"
        config_file = voicerun_dir / "config.json"

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import set_current_context
            set_current_context("custom", "http://localhost:8080")

        data = json.loads(config_file.read_text())
        assert data["current_context"] == "custom"
        assert data["custom_api_url"] == "http://localhost:8080"

    def test_get_all_contexts_includes_user_defined(self, temp_dir):
        """Should include user-defined contexts."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({
            "user_contexts": {
                "staging": {
                    "api_url": "https://api.staging.com",
                    "frontend_url": "https://app.staging.com"
                }
            }
        }))

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import get_all_contexts
            result = get_all_contexts()

        assert "default" in result
        assert "staging" in result

    def test_create_context_saves_to_file(self, temp_dir):
        """Should save new context to config file."""
        voicerun_dir = temp_dir / ".voicerun"
        config_file = voicerun_dir / "config.json"

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import create_context
            create_context("local", "http://localhost:8080", "http://localhost:3000")

        data = json.loads(config_file.read_text())
        assert "local" in data["user_contexts"]
        assert data["user_contexts"]["local"]["api_url"] == "http://localhost:8080"

    def test_delete_context_removes_from_file(self, temp_dir):
        """Should remove context from config file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({
            "current_context": "default",
            "user_contexts": {
                "staging": {
                    "api_url": "https://api.staging.com",
                    "frontend_url": "https://app.staging.com"
                }
            }
        }))

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import delete_context
            success, message = delete_context("staging")

        assert success is True
        data = json.loads(config_file.read_text())
        assert "staging" not in data.get("user_contexts", {})

    def test_delete_context_switches_to_default(self, temp_dir):
        """Should switch to default when deleting current context."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({
            "current_context": "staging",
            "user_contexts": {
                "staging": {
                    "api_url": "https://api.staging.com",
                    "frontend_url": "https://app.staging.com"
                }
            }
        }))

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import delete_context
            delete_context("staging")

        data = json.loads(config_file.read_text())
        assert data["current_context"] == "default"


class TestContextEdgeCases:
    """Edge case tests for context commands."""

    def test_list_contexts_with_custom_url(self):
        """Should show custom URL in list."""
        with patch("vr_cli.commands.context.get_current_context") as mock_current, \
             patch("vr_cli.commands.context.get_all_contexts") as mock_all, \
             patch("vr_cli.commands.context.get_context_urls") as mock_urls, \
             patch("vr_cli.commands.context.console") as mock_console:

            mock_current.return_value = "custom"
            mock_all.return_value = {"default": {"api_url": "x", "frontend_url": "y"}}
            mock_urls.return_value = ("http://localhost:8080", "http://localhost:3000")

            result = runner.invoke(app, ["context", "list"])

        # Table should include custom context

    def test_context_invalid_json_config(self, temp_dir):
        """Should handle corrupted config file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text("not valid json {")

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import get_current_context
            result = get_current_context()

        # Should fall back to default
        assert result == "default"


class TestOrganizationIdPerContext:
    """Tests for per-context organization ID storage."""

    def test_set_and_get_org_id(self, temp_dir):
        """Should store org ID in a context-specific file."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({"current_context": "staging"}))

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import set_organization_id, get_organization_id
            set_organization_id("org-123")
            assert get_organization_id() == "org-123"

        # Stored in file, not config.json
        assert (voicerun_dir / "staging.orgid").read_text() == "org-123"

    def test_org_id_does_not_carry_over_between_contexts(self, temp_dir):
        """Org ID set on one context should not be visible on another."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({
            "current_context": "staging",
            "user_contexts": {
                "staging": {
                    "api_url": "https://api.staging.com",
                    "frontend_url": "https://app.staging.com"
                },
                "local": {
                    "api_url": "http://localhost:8080",
                    "frontend_url": "http://localhost:3000"
                }
            }
        }))

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import set_organization_id, get_organization_id, set_current_context

            set_organization_id("org-staging")
            assert get_organization_id() == "org-staging"

            set_current_context("local")
            assert get_organization_id() is None

    def test_clear_org_id(self, temp_dir):
        """Should delete the org ID file when cleared."""
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir(parents=True, exist_ok=True)
        config_file = voicerun_dir / "config.json"
        config_file.write_text(json.dumps({"current_context": "default"}))
        (voicerun_dir / "default.orgid").write_text("org-123")

        with patch("vr_cli.utils.config.VOICERUN_DIR", voicerun_dir), \
             patch("vr_cli.utils.config.CONTEXT_CONFIG_FILE", config_file):
            from vr_cli.utils.config import clear_organization_id, get_organization_id
            clear_organization_id()
            assert get_organization_id() is None

        assert not (voicerun_dir / "default.orgid").exists()
