"""Tests for vr deploy command."""

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.deploy import (
    deploy,
    prompt_for_environment,
    resolve_environment,
    run_deploy_validation,
)
from vr_cli.entities.agent_environment import AgentEnvironment


runner = CliRunner()


class TestRunDeployValidation:
    """Tests for run_deploy_validation function."""

    def test_validation_passes_for_valid_project(self, voicerun_project):
        """Should return True for valid project."""
        with patch("vr_cli.commands.deploy.console"):
            result = run_deploy_validation(voicerun_project)
        assert result is True

    def test_validation_fails_for_missing_voicerun_dir(self, temp_dir):
        """Should return False when .voicerun/ is missing."""
        (temp_dir / "handler.py").write_text("async def handler(event): pass\n")

        with patch("vr_cli.commands.deploy.console"):
            result = run_deploy_validation(temp_dir)
        assert result is False


class TestResolveEnvironment:
    """Tests for resolve_environment function."""

    def test_resolve_by_name(self):
        """Should resolve environment by name."""
        mock_env = AgentEnvironment(id="env-123", name="development")
        mock_repo = Mock()
        mock_repo.get_by_name_or_id.return_value = mock_env

        with patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):
            result = resolve_environment("agent-123", "development")

        assert result is mock_env
        mock_repo.get_by_name_or_id.assert_called_once_with("development")

    def test_resolve_by_id(self):
        """Should resolve environment by UUID."""
        env_id = "550e8400-e29b-41d4-a716-446655440000"
        mock_env = AgentEnvironment(id=env_id, name="production")
        mock_repo = Mock()
        mock_repo.get_by_name_or_id.return_value = mock_env

        with patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):
            result = resolve_environment("agent-123", env_id)

        assert result is mock_env

    def test_resolve_not_found(self):
        """Should return None when environment not found."""
        mock_repo = Mock()
        mock_repo.get_by_name_or_id.return_value = None

        with patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo), \
             patch("vr_cli.commands.deploy.print_error"), \
             patch("vr_cli.commands.deploy.print_info"):
            result = resolve_environment("agent-123", "nonexistent")

        assert result is None


class TestPromptForEnvironment:
    """Tests for prompt_for_environment function."""

    def test_no_environments_available(self):
        """Should return None when no environments exist on the agent."""
        mock_repo = Mock()
        mock_repo.get.return_value = []

        with patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo), \
             patch("vr_cli.commands.deploy.print_error"), \
             patch("vr_cli.commands.deploy.print_info"):
            result = prompt_for_environment("agent-123")

        assert result is None

    def test_select_environment_from_list(self):
        """Should return selected AgentEnvironment from interactive list."""
        mock_env = AgentEnvironment(
            id="env-123",
            name="staging",
            phone_number="+15555555555",
            stt_model="nova-3",
        )

        mock_repo = Mock()
        mock_repo.get.return_value = [mock_env]

        mock_select = Mock()
        mock_select.ask.return_value = mock_env

        with patch("vr_cli.commands.deploy.sys.stdin") as mock_stdin, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo), \
             patch("vr_cli.commands.deploy.questionary.select", return_value=mock_select):
            mock_stdin.isatty.return_value = True
            result = prompt_for_environment("agent-123")

        assert result is mock_env
        assert result.name == "staging"
        assert result.id == "env-123"

    def test_select_from_multiple_environments(self):
        """Should allow user to select from multiple environments."""
        mock_env1 = AgentEnvironment(
            id="env-1",
            name="development",
            phone_number="+15555555551",
            stt_model="nova-3",
        )
        mock_env2 = AgentEnvironment(
            id="env-2",
            name="production",
            phone_number="+15555555552",
            stt_model="nova-3",
        )

        mock_repo = Mock()
        mock_repo.get.return_value = [mock_env1, mock_env2]

        mock_select = Mock()
        mock_select.ask.return_value = mock_env2

        with patch("vr_cli.commands.deploy.sys.stdin") as mock_stdin, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo), \
             patch("vr_cli.commands.deploy.questionary.select", return_value=mock_select):
            mock_stdin.isatty.return_value = True
            result = prompt_for_environment("agent-123")

        assert result is mock_env2
        assert result.name == "production"

    def test_user_cancels_selection(self):
        """Should return None when user cancels (Ctrl+C)."""
        mock_env = AgentEnvironment(
            id="env-1",
            name="development",
            phone_number="+15555555551",
            stt_model="nova-3",
        )

        mock_repo = Mock()
        mock_repo.get.return_value = [mock_env]

        mock_select = Mock()
        mock_select.ask.return_value = None  # User cancelled

        with patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo), \
             patch("vr_cli.commands.deploy.questionary.select", return_value=mock_select):
            result = prompt_for_environment("agent-123")

        assert result is None

    def test_environment_without_phone_or_model(self):
        """Should handle environments with missing optional fields."""
        mock_env = AgentEnvironment(
            id="env-1",
            name="minimal",
            phone_number=None,
            stt_model=None,
        )

        mock_repo = Mock()
        mock_repo.get.return_value = [mock_env]

        mock_select = Mock()
        mock_select.ask.return_value = mock_env

        with patch("vr_cli.commands.deploy.sys.stdin") as mock_stdin, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo), \
             patch("vr_cli.commands.deploy.questionary.select", return_value=mock_select):
            mock_stdin.isatty.return_value = True
            result = prompt_for_environment("agent-123")

        assert result is mock_env
        assert result.name == "minimal"


class TestDeployCommand:
    """Tests for the deploy command."""

    def test_deploy_not_voicerun_project(self, temp_dir, monkeypatch):
        """Should fail when not in a voicerun project."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.deploy.print_error"), \
             patch("vr_cli.commands.deploy.print_info"):
            result = runner.invoke(app, ["deploy", "development"])

        assert result.exit_code != 0

    def test_deploy_no_agent_lock(self, voicerun_project, monkeypatch):
        """Should fail when agent.lock doesn't exist."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.deploy.print_error"), \
             patch("vr_cli.commands.deploy.print_info"):
            result = runner.invoke(app, ["deploy", "development"])

        assert result.exit_code != 0

    def test_deploy_success(self, voicerun_project_with_lock, monkeypatch):
        """Should deploy successfully."""
        monkeypatch.chdir(voicerun_project_with_lock)

        mock_env = AgentEnvironment(id="env-123", name="development")
        mock_repo = Mock()
        mock_repo.deploy_function.return_value = mock_env

        with patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.print_success"), \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.resolve_environment") as mock_resolve, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):

            mock_validate.return_value = True
            mock_resolve.return_value = mock_env

            result = runner.invoke(app, ["deploy", "development", "--yes"])

        assert result.exit_code == 0
        mock_repo.deploy_function.assert_called_once_with("env-123", "test-function-id-456")

    def test_deploy_skip_confirmation(self, voicerun_project_with_lock, monkeypatch):
        """Should skip confirmation with --yes flag."""
        monkeypatch.chdir(voicerun_project_with_lock)

        mock_env = AgentEnvironment(id="env-123", name="development")
        mock_repo = Mock()
        mock_repo.deploy_function.return_value = mock_env

        with patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.print_success"), \
             patch("vr_cli.commands.deploy.confirm") as mock_confirm, \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.resolve_environment") as mock_resolve, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):

            mock_validate.return_value = True
            mock_resolve.return_value = mock_env

            result = runner.invoke(app, ["deploy", "development", "--yes"])

        # Confirm should not be called with --yes
        mock_confirm.assert_not_called()
        assert result.exit_code == 0

    def test_deploy_aborted_by_user(self, voicerun_project_with_lock, monkeypatch):
        """Should abort when user declines confirmation."""
        monkeypatch.chdir(voicerun_project_with_lock)

        mock_env = AgentEnvironment(id="env-123", name="development")

        with patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.confirm") as mock_confirm, \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.resolve_environment") as mock_resolve, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository") as mock_repo_cls:

            mock_confirm.return_value = False
            mock_validate.return_value = True
            mock_resolve.return_value = mock_env

            result = runner.invoke(app, ["deploy", "development"])

        # deploy_function should not be called when user aborts
        mock_repo_cls.return_value.deploy_function.assert_not_called()

    def test_deploy_api_failure(self, voicerun_project_with_lock, monkeypatch):
        """Should handle API failure gracefully."""
        monkeypatch.chdir(voicerun_project_with_lock)

        mock_env = AgentEnvironment(id="env-123", name="development")
        mock_repo = Mock()
        mock_repo.deploy_function.return_value = None  # API failure

        with patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.print_error"), \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.resolve_environment") as mock_resolve, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):

            mock_validate.return_value = True
            mock_resolve.return_value = mock_env

            result = runner.invoke(app, ["deploy", "development", "--yes"])

        assert result.exit_code != 0

    def test_deploy_environment_not_found(self, voicerun_project_with_lock, monkeypatch):
        """Should fail when environment is not found."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.print_error"), \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.resolve_environment") as mock_resolve:

            mock_validate.return_value = True
            mock_resolve.return_value = None

            result = runner.invoke(app, ["deploy", "nonexistent", "--yes"])

        assert result.exit_code != 0

    def test_deploy_correct_payload(self, voicerun_project_with_lock, monkeypatch):
        """Should send correct functionId and deploymentAction to the API."""
        monkeypatch.chdir(voicerun_project_with_lock)

        mock_env = AgentEnvironment(id="env-xyz", name="staging")
        mock_repo = Mock()
        mock_repo.deploy_function.return_value = mock_env

        with patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.print_success"), \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.resolve_environment") as mock_resolve, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):

            mock_validate.return_value = True
            mock_resolve.return_value = mock_env

            result = runner.invoke(app, ["deploy", "staging", "--yes"])

        assert result.exit_code == 0
        mock_repo.deploy_function.assert_called_once_with("env-xyz", "test-function-id-456")


class TestDeployInteractiveSelection:
    """Tests for deploy command with interactive environment selection."""

    def test_deploy_without_environment_prompts_user(self, voicerun_project_with_lock, monkeypatch):
        """Should prompt for environment when not provided."""
        monkeypatch.chdir(voicerun_project_with_lock)

        mock_env = AgentEnvironment(id="env-123", name="development")
        mock_repo = Mock()
        mock_repo.deploy_function.return_value = mock_env

        with patch("vr_cli.commands.deploy.prompt_for_environment") as mock_prompt_env, \
             patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.print_success"), \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate, \
             patch("vr_cli.commands.deploy.AgentEnvironmentRepository", return_value=mock_repo):

            mock_prompt_env.return_value = mock_env
            mock_validate.return_value = True

            result = runner.invoke(app, ["deploy", "--yes"])

        assert result.exit_code == 0
        mock_prompt_env.assert_called_once()
        mock_repo.deploy_function.assert_called_once_with("env-123", "test-function-id-456")

    def test_deploy_without_environment_user_cancels(self, voicerun_project_with_lock, monkeypatch):
        """Should exit when user cancels environment selection."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.deploy.prompt_for_environment") as mock_prompt_env, \
             patch("vr_cli.commands.deploy.console"), \
             patch("vr_cli.commands.deploy.print_info"), \
             patch("vr_cli.commands.deploy.run_deploy_validation") as mock_validate:

            mock_prompt_env.return_value = None
            mock_validate.return_value = True

            result = runner.invoke(app, ["deploy", "--yes"])

        assert result.exit_code != 0
