"""Tests for vr auth commands."""

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

from typer.testing import CliRunner

from vr_cli.cli import app
from vr_cli.commands.auth import (
    signin,
    signin_email_password,
    signout,
    signin_api_key,
    signin_web,
)


runner = CliRunner()


class TestSigninCommand:
    """Tests for auth signin command."""

    def test_signin_already_authenticated(self):
        """Should succeed if already signed in."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.print_success"):

            mock_response = Mock()
            mock_response.status_code = 200
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, ["signin"], input="1\n")

        # Should report already signed in

    def test_signin_prompts_for_method(self):
        """Should prompt for authentication method."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.signin_web") as mock_web, \
             patch("vr_cli.commands.auth.print_info"):

            mock_response = Mock()
            mock_response.status_code = 401  # Not authenticated
            mock_session.return_value.get.return_value = mock_response
            mock_prompt.return_value = "1"  # Select web auth

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

        mock_web.assert_called_once()

    def test_signin_web_method(self):
        """Should call signin_web when method 1 is selected."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.signin_web") as mock_web, \
             patch("vr_cli.commands.auth.print_info"):

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response
            mock_prompt.return_value = "1"

            runner.invoke(app, ["signin"])

        mock_web.assert_called_once()

    def test_signin_api_key_method(self):
        """Should call signin_api_key when method 3 is selected."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.signin_api_key") as mock_apikey, \
             patch("vr_cli.commands.auth.print_info"):

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response
            mock_prompt.return_value = "3"

            runner.invoke(app, ["signin"])

        mock_apikey.assert_called_once()

    def test_signin_email_password(self):
        """Should authenticate with email/password when method 2 is selected."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.save_cookie") as mock_save_cookie, \
             patch("vr_cli.commands.auth.print_info"), \
             patch("vr_cli.commands.auth.print_success"):

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            mock_prompt.side_effect = ["2", "test@example.com", "password123"]

            post_response = Mock()
            post_response.status_code = 204
            post_response.headers = {"Set-Cookie": "appSession=abc123; Path=/"}
            mock_requests.post.return_value = post_response

            runner.invoke(app, ["signin"])

        mock_save_cookie.assert_called_once()


class TestSigninNonInteractive:
    """Tests for non-interactive signin via CLI options."""

    def test_signin_with_api_key_option(self):
        """Should authenticate with --api-key without prompting."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.signin_api_key") as mock_signin_api_key:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, ["signin", "--api-key", "test-key-123"])

        mock_signin_api_key.assert_called_once_with("test-key-123")

    def test_signin_with_api_key_short_flag(self):
        """Should authenticate with -k short flag."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.signin_api_key") as mock_signin_api_key:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, ["signin", "-k", "test-key-123"])

        mock_signin_api_key.assert_called_once_with("test-key-123")

    def test_signin_with_email_and_password(self):
        """Should authenticate with --email and --password without prompting."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.signin_email_password") as mock_signin_ep:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, [
                "signin", "--email", "test@example.com", "--password", "pass123"
            ])

        mock_signin_ep.assert_called_once_with("test@example.com", "pass123")

    def test_signin_with_email_and_password_short_flags(self):
        """Should authenticate with -e and -p short flags."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.signin_email_password") as mock_signin_ep:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, [
                "signin", "-e", "test@example.com", "-p", "pass123"
            ])

        mock_signin_ep.assert_called_once_with("test@example.com", "pass123")

    def test_signin_email_without_password_errors(self):
        """Should error when --email provided without --password."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.print_error") as mock_error:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, ["signin", "--email", "test@example.com"])

        assert result.exit_code == 1

    def test_signin_password_without_email_errors(self):
        """Should error when --password provided without --email."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.print_error") as mock_error:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            result = runner.invoke(app, ["signin", "--password", "pass123"])

        assert result.exit_code == 1


class TestSigninWebFunction:
    """Tests for signin_web function."""

    def test_signin_web_success(self):
        """Should complete web signin flow."""
        with patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.open_browser") as mock_browser, \
             patch("vr_cli.commands.auth.save_cookie") as mock_save, \
             patch("vr_cli.commands.auth.print_info"), \
             patch("vr_cli.commands.auth.print_success"):

            # Initial request for login token
            init_response = Mock()
            init_response.status_code = 200
            init_response.json.return_value = {
                "login_token": "token123",
                "login_url": "https://example.com/login?token=token123"
            }

            # Poll response - completed
            poll_response = Mock()
            poll_response.status_code = 200
            poll_response.json.return_value = {
                "status": "complete",
                "session_cookie": "session123"
            }

            mock_requests.post.return_value = init_response
            mock_requests.get.return_value = poll_response

            signin_web()

        mock_browser.assert_called_once()
        mock_save.assert_called_once_with("appSession=session123")

    def test_signin_web_timeout(self):
        """Should handle timeout when login not completed."""
        with patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.open_browser"), \
             patch("vr_cli.commands.auth.print_info"), \
             patch("vr_cli.commands.auth.print_warning") as mock_warning, \
             patch("vr_cli.commands.auth.MAX_POLL_ATTEMPTS", 1), \
             patch("vr_cli.commands.auth.POLL_INTERVAL", 0):

            init_response = Mock()
            init_response.status_code = 200
            init_response.json.return_value = {
                "login_token": "token123",
                "login_url": "https://example.com/login"
            }

            poll_response = Mock()
            poll_response.status_code = 200
            poll_response.json.return_value = {"status": "pending"}

            mock_requests.post.return_value = init_response
            mock_requests.get.return_value = poll_response

            signin_web()

        mock_warning.assert_called()

    def test_signin_web_missing_token(self):
        """Should handle missing login token in response."""
        with patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.print_error") as mock_error:

            response = Mock()
            response.status_code = 200
            response.json.return_value = {}  # Missing token and URL
            mock_requests.post.return_value = response

            signin_web()

        mock_error.assert_called()


class TestSigninApiKeyFunction:
    """Tests for signin_api_key function."""

    def test_signin_api_key_success(self):
        """Should save API key on successful authentication."""
        import requests as real_requests

        with patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.requests.Session") as mock_session_class, \
             patch("vr_cli.commands.auth.save_cookie") as mock_save_cookie, \
             patch("vr_cli.commands.auth.save_api_key") as mock_save_key, \
             patch("vr_cli.commands.auth.print_success"):

            mock_prompt.return_value = "test-api-key"

            mock_session = Mock()
            mock_session.headers = {}
            mock_response = Mock()
            mock_response.status_code = 200
            mock_session.get.return_value = mock_response
            mock_session_class.return_value = mock_session

            signin_api_key()

        mock_save_key.assert_called_once_with("test-api-key")
        mock_save_cookie.assert_called_once_with("")  # Clear cookie

    def test_signin_api_key_invalid(self):
        """Should handle invalid API key."""
        with patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.print_error") as mock_error, \
             patch("vr_cli.commands.auth.handle_http_error"):

            mock_prompt.return_value = "invalid-key"

            mock_session = Mock()
            mock_response = Mock()
            mock_response.status_code = 401
            mock_response.raise_for_status.side_effect = Exception("Unauthorized")
            mock_session.get.return_value = mock_response
            mock_requests.Session.return_value = mock_session
            mock_requests.HTTPError = Exception

            signin_api_key()

        mock_error.assert_called()


class TestSignoutCommand:
    """Tests for auth signout command."""

    def test_signout_clears_credentials(self):
        """Should clear both cookie and API key."""
        with patch("vr_cli.commands.auth.get_authenticated_session"), \
             patch("vr_cli.commands.auth.save_cookie") as mock_save_cookie, \
             patch("vr_cli.commands.auth.save_api_key") as mock_save_key, \
             patch("vr_cli.commands.auth.print_success"):

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

        mock_save_cookie.assert_called_once_with("")
        mock_save_key.assert_called_once_with("")


class TestAuthEdgeCases:
    """Edge case tests for auth commands."""

    def test_signin_http_error(self):
        """Should handle HTTP errors during signin."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.handle_http_error") as mock_handle, \
             patch("vr_cli.commands.auth.print_info"):

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            mock_prompt.side_effect = ["2", "test@example.com", "password123"]

            import requests
            mock_requests.post.side_effect = requests.HTTPError("Error")
            mock_requests.HTTPError = requests.HTTPError

            runner.invoke(app, ["signin"])

        mock_handle.assert_called()

    def test_signin_no_set_cookie_header(self):
        """Should warn when no Set-Cookie header received."""
        with patch("vr_cli.commands.auth.get_authenticated_session") as mock_session, \
             patch("vr_cli.commands.auth.prompt") as mock_prompt, \
             patch("vr_cli.commands.auth.requests") as mock_requests, \
             patch("vr_cli.commands.auth.print_info"), \
             patch("vr_cli.commands.auth.print_warning") as mock_warning:

            mock_response = Mock()
            mock_response.status_code = 401
            mock_session.return_value.get.return_value = mock_response

            mock_prompt.side_effect = ["2", "test@example.com", "password123"]

            post_response = Mock()
            post_response.status_code = 204
            post_response.headers = {}  # No Set-Cookie
            mock_requests.post.return_value = post_response

            runner.invoke(app, ["signin"])

        mock_warning.assert_called()
