"""Unit tests for products CLI commands."""

import os
from unittest.mock import Mock, patch

from click.testing import CliRunner

from augint_billing_lib.cli.products.list_products import list_products
from augint_billing_lib.cli.products.setup import setup_products
from augint_billing_lib.cli.products.show import show_product
from augint_billing_lib.cli.products.verify import verify_products


class TestProductsSetupCommand:
    """Test products setup command."""

    def test_uses_config_system(self):
        """Test that setup command uses the new config system."""
        # The setup command now uses the CliConfig system
        # Verify the module imports successfully and has the config
        import augint_billing_lib.cli.products.setup

        # Check that the module uses the new config system
        assert hasattr(augint_billing_lib.cli.products.setup, "get_config")

    @patch("stripe.Product")
    @patch("stripe.Price")
    def test_setup_subscription_plus_metered(self, mock_price, mock_product):
        """Test setup with subscription-plus-metered model."""
        runner = CliRunner()

        # Mock environment
        with patch.dict(os.environ, {"STAGING_STRIPE_SECRET_KEY": "sk_test_mock"}):
            # Mock Stripe responses
            mock_product.list.return_value = Mock(data=[])
            mock_product.create.return_value = Mock(id="prod_test123", name="Test Product")
            mock_product.retrieve.return_value = Mock(id="prod_test123")

            mock_price.list.return_value = Mock(data=[])
            mock_price.create.return_value = Mock(id="price_test123")

            result = runner.invoke(
                setup_products,
                ["staging", "--model", "subscription-plus-metered", "--dry-run"],
            )

            assert result.exit_code == 0
            assert "DRY RUN" in result.output
            assert "subscription-plus-metered" in result.output

    @patch("stripe.Product")
    @patch("stripe.Price")
    def test_setup_tier_calculation_subscription_plus_metered(self, mock_price, mock_product):
        """Test that tier calculation is correct for subscription-plus-metered."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STAGING_STRIPE_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(data=[])
            mock_product.create.return_value = Mock(id="prod_test123")
            mock_product.retrieve.return_value = Mock(id="prod_test123")

            mock_price.list.return_value = Mock(data=[])

            # Capture the tiers passed to Price.create
            captured_tiers = []

            def capture_create(**kwargs):
                if kwargs.get("billing_scheme") == "tiered":
                    captured_tiers.extend(kwargs.get("tiers", []))
                return Mock(id="price_test123")

            mock_price.create.side_effect = capture_create

            result = runner.invoke(
                setup_products,
                [
                    "staging",
                    "--model",
                    "subscription-plus-metered",
                    "--included-requests",
                    "1000",
                    "--tier-1-limit",
                    "10000",
                    "--tier-2-limit",
                    "100000",
                ],
            )

            assert result.exit_code == 0

            # Verify tier structure
            # With 1000 included requests:
            # Tier 1: 0 to 9000 (covers usage 1001-10000)
            # Tier 2: 9000 to 99000 (covers usage 10001-100000)
            # Tier 3: infinite (covers 100001+)
            assert len(captured_tiers) == 3
            assert captured_tiers[0]["up_to"] == 9000  # 10000 - 1000
            assert captured_tiers[1]["up_to"] == 99000  # 100000 - 1000
            # Tier 3 should have up_to set to "inf" (Stripe accepts this)
            assert captured_tiers[2]["up_to"] == "inf"

    @patch("stripe.Product")
    @patch("stripe.Price")
    def test_setup_tier_calculation_metered_only(self, mock_price, mock_product):
        """Test that tier calculation is correct for metered-only."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STAGING_STRIPE_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(data=[])
            mock_product.create.return_value = Mock(id="prod_test123")
            mock_product.retrieve.return_value = Mock(id="prod_test123")

            mock_price.list.return_value = Mock(data=[])

            # Capture the tiers passed to Price.create
            captured_tiers = []

            def capture_create(**kwargs):
                if kwargs.get("billing_scheme") == "tiered":
                    captured_tiers.extend(kwargs.get("tiers", []))
                return Mock(id="price_test123")

            mock_price.create.side_effect = capture_create

            result = runner.invoke(
                setup_products,
                [
                    "staging",
                    "--model",
                    "metered-only",
                    "--tier-1-limit",
                    "10000",
                    "--tier-2-limit",
                    "100000",
                ],
            )

            assert result.exit_code == 0

            # Verify tier structure for metered-only (no included requests)
            # Tier 1: 0 to 10000
            # Tier 2: 10000 to 100000
            # Tier 3: infinite
            assert len(captured_tiers) == 3
            assert captured_tiers[0]["up_to"] == 10000
            assert captured_tiers[1]["up_to"] == 100000
            # Tier 3 should not have up_to (infinite)
            assert captured_tiers[2]["up_to"] == "inf"

    @patch("stripe.Product")
    def test_setup_verifies_product_creation(self, mock_product):
        """Test that setup verifies product was actually created."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STAGING_STRIPE_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(data=[])
            created_product = Mock(id="prod_test123")
            mock_product.create.return_value = created_product

            # Simulate verification succeeding
            mock_product.retrieve.return_value = created_product

            with patch("stripe.Price") as mock_price:
                mock_price.list.return_value = Mock(data=[])
                mock_price.create.return_value = Mock(id="price_test123")

                result = runner.invoke(
                    setup_products,
                    ["staging", "--model", "subscription-only"],
                )

                assert result.exit_code == 0
                # Should have called retrieve to verify
                mock_product.retrieve.assert_called_with("prod_test123")
                assert "Created product in Stripe: prod_test123" in result.output

    @patch("stripe.api_version", None)
    @patch("stripe.Product")
    def test_setup_does_not_set_api_version(self, mock_product):
        """Test that setup does NOT set Stripe API version (uses latest)."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STAGING_STRIPE_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(data=[])

            # Import stripe to check api_version is NOT set
            import stripe

            initial_version = stripe.api_version

            result = runner.invoke(
                setup_products,
                ["staging", "--dry-run"],
            )

            assert result.exit_code == 0
            # API version should NOT be changed (remains as initial or None)
            assert stripe.api_version == initial_version


class TestProductsVerifyCommand:
    """Test products verify command."""

    def test_uses_config_system(self):
        """Test that verify command uses the new config system."""
        # The verify command now uses the CliConfig system
        # Verify the module imports successfully and has the config
        import augint_billing_lib.cli.products.verify

        # Check that the module uses the new config system
        assert hasattr(augint_billing_lib.cli.products.verify, "get_config")

    @patch("stripe.Product")
    @patch("stripe.Price")
    def test_verify_checks_env_vars(self, mock_price, mock_product):
        """Test verify command checks environment variables."""
        runner = CliRunner()

        env_vars = {
            "STAGING_STRIPE_SECRET_KEY": "sk_test_mock",
            "STAGING_API_USAGE_PRODUCT_ID": "prod_test123",
            "STAGING_BASE_SUBSCRIPTION_PRICE_ID": "price_sub123",
            "STAGING_METERED_USAGE_PRICE_ID": "price_metered123",
        }

        with patch.dict(os.environ, env_vars):
            # Reset config to pick up new environment
            from augint_billing_lib.cli.config import reset_config

            reset_config()

            mock_product.retrieve.return_value = Mock(
                id="prod_test123",
                name="Test Product",
                metadata={"environment": "staging"},
            )
            mock_price.retrieve.side_effect = [
                Mock(
                    id="price_sub123",
                    type="recurring",
                    recurring={"interval": "month", "usage_type": "licensed"},
                    unit_amount=2999,
                    currency="usd",
                ),
                Mock(
                    id="price_metered123",
                    type="recurring",
                    recurring={"interval": "month", "usage_type": "metered"},
                    billing_scheme="tiered",
                    tiers=[],
                ),
            ]

            result = runner.invoke(verify_products, ["staging"])

            assert result.exit_code == 0
            assert "✅" in result.output
            assert "STAGING_API_USAGE_PRODUCT_ID" in result.output
            assert "subscription-plus-metered" in result.output.lower()

    @patch("stripe.Product")
    def test_verify_detects_missing_products(self, mock_product):
        """Test verify detects when products don't exist in Stripe."""
        runner = CliRunner()

        env_vars = {
            "STAGING_STRIPE_SECRET_KEY": "sk_test_mock",
            "STAGING_API_USAGE_PRODUCT_ID": "prod_nonexistent",
        }

        with patch.dict(os.environ, env_vars):
            from stripe.error import InvalidRequestError

            mock_product.retrieve.side_effect = InvalidRequestError("No such product", param="id")

            result = runner.invoke(verify_products, ["staging"])

            assert result.exit_code == 0
            assert "❌" in result.output
            assert "not found in Stripe" in result.output


class TestProductsListCommand:
    """Test products list command."""

    def test_uses_config_system(self):
        """Test that list command uses the new config system."""
        # The list command now uses the CliConfig system
        # Just verify the module imports successfully
        from augint_billing_lib.cli.products import list_products

        # The module should have successfully imported
        # We can't check for the attribute on the Click command object,
        # but we can verify it imported without errors
        assert list_products is not None

    @patch("stripe.Product")
    @patch("stripe.Price")
    def test_list_products(self, mock_price, mock_product):
        """Test listing products."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STRIPE_TEST_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(
                data=[
                    Mock(
                        id="prod_test1",
                        name="Product 1",
                        active=True,
                        description="Test product",
                        metadata={"created_by": "ai-billing-cli", "environment": "staging"},
                    ),
                    Mock(
                        id="prod_test2",
                        name="Product 2",
                        active=False,
                        description=None,
                        metadata={},
                    ),
                ],
                has_more=False,
            )

            result = runner.invoke(list_products, ["--environment", "staging"])

            assert result.exit_code == 0
            assert "Product 1" in result.output
            assert "Product 2" in result.output
            assert "prod_test1" in result.output
            assert "✅" in result.output  # Active product
            assert "❌" in result.output  # Inactive product

    @patch("stripe.Product")
    def test_list_filters_by_cli(self, mock_product):
        """Test filtering products created by CLI."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STRIPE_TEST_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(
                data=[
                    Mock(
                        id="prod_cli",
                        name="CLI Product",
                        active=True,
                        metadata={"created_by": "ai-billing-cli"},
                    ),
                    Mock(
                        id="prod_other",
                        name="Other Product",
                        active=True,
                        metadata={},
                    ),
                ],
                has_more=False,
            )

            result = runner.invoke(list_products, ["--filter-created-by-cli"])

            assert result.exit_code == 0
            assert "CLI Product" in result.output
            assert "Other Product" not in result.output

    @patch("stripe.Product")
    def test_list_displays_all_metadata_including_service(self, mock_product):
        """Test that list command displays ALL metadata fields including service."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STRIPE_TEST_SECRET_KEY": "sk_test_mock"}):
            mock_product.list.return_value = Mock(
                data=[
                    Mock(
                        id="prod_test",
                        name="Test Product",
                        active=True,
                        description="Test product with all metadata",
                        metadata={
                            "created_by": "ai-billing-cli",
                            "environment": "staging",
                            "model": "subscription-plus-metered",
                            "service": "augint-billing",
                            "custom_field": "custom_value",
                        },
                    ),
                ],
                has_more=False,
            )

            result = runner.invoke(list_products, ["--environment", "staging"])

            assert result.exit_code == 0
            # Verify all required metadata fields are displayed
            assert "service: augint-billing" in result.output
            assert "environment: staging" in result.output
            assert "model: subscription-plus-metered" in result.output
            assert "created_by: ai-billing-cli" in result.output
            # Also verify custom fields are shown
            assert "custom_field: custom_value" in result.output


class TestProductsShowCommand:
    """Test products show command."""

    def test_uses_config_system(self):
        """Test that show command uses the new config system."""
        # The show command now uses the CliConfig system
        # Verify the module imports successfully and has the config
        import augint_billing_lib.cli.products.show

        # Check that the module uses the new config system
        assert hasattr(augint_billing_lib.cli.products.show, "get_config")

    @patch("stripe.Product")
    @patch("stripe.Price")
    def test_show_product_details(self, mock_price, mock_product):
        """Test showing product details."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STRIPE_SECRET_KEY": "sk_test_mock"}):
            mock_product.retrieve.return_value = Mock(
                id="prod_test123",
                name="Test Product",
                description="Test description",
                active=True,
                metadata={"environment": "staging"},
                created=1234567890,
            )

            mock_price.list.return_value = Mock(
                data=[
                    Mock(
                        id="price_sub123",
                        active=True,
                        currency="usd",
                        type="recurring",
                        recurring={"interval": "month", "usage_type": "licensed"},
                        unit_amount=2999,
                        metadata={},
                    ),
                    Mock(
                        id="price_metered123",
                        active=True,
                        currency="usd",
                        type="recurring",
                        recurring={"interval": "month", "usage_type": "metered"},
                        billing_scheme="tiered",
                        tiers=[
                            {"up_to": 1000, "unit_amount": 10},
                            {"up_to": "inf", "unit_amount": 5},
                        ],
                        metadata={},
                    ),
                ],
            )

            result = runner.invoke(show_product, ["prod_test123"])

            assert result.exit_code == 0
            assert "Test Product" in result.output
            assert "prod_test123" in result.output
            assert "$29.99/month" in result.output
            assert "Metered billing" in result.output
            assert "tier" in result.output.lower()

    @patch("stripe.Product")
    def test_show_product_not_found(self, mock_product):
        """Test showing non-existent product."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STRIPE_SECRET_KEY": "sk_test_mock"}):
            from stripe.error import InvalidRequestError

            mock_product.retrieve.side_effect = InvalidRequestError(
                "No such product", param="id", http_status=404
            )

            result = runner.invoke(show_product, ["prod_nonexistent"])

            assert result.exit_code == 1
            assert "not found" in result.output


class TestEnvironmentVariableLoading:
    """Test environment variable loading from .env file."""

    def test_env_loading_integration(self, tmp_path):
        """Test that .env file is actually loaded."""
        # Create a temporary .env file
        env_file = tmp_path / ".env"
        env_file.write_text("TEST_STRIPE_KEY=sk_test_from_dotenv\n")

        # Save original environment
        original_value = os.environ.get("TEST_STRIPE_KEY")

        try:
            # Clear environment
            os.environ.pop("TEST_STRIPE_KEY", None)

            # Load dotenv with explicit path
            from dotenv import load_dotenv

            load_dotenv(dotenv_path=env_file)

            # Check that variable was loaded
            assert os.getenv("TEST_STRIPE_KEY") == "sk_test_from_dotenv"

        finally:
            # Restore original environment
            if original_value is not None:
                os.environ["TEST_STRIPE_KEY"] = original_value
            else:
                os.environ.pop("TEST_STRIPE_KEY", None)


class TestStripeAPIVersion:
    """Test Stripe API version handling."""

    @patch("stripe.Product")
    def test_api_version_not_set(self, mock_product):
        """Test that API version is NOT set (uses latest API)."""
        runner = CliRunner()

        with patch.dict(os.environ, {"STRIPE_SECRET_KEY": "sk_test_mock"}):
            import stripe

            # Clear any existing version
            stripe.api_version = None

            mock_product.list.return_value = Mock(data=[], has_more=False)

            result = runner.invoke(list_products)

            assert result.exit_code == 0
            # Should NOT set API version (uses latest)
            assert stripe.api_version is None
