# Functional test for physical product handling option selection
# Tests the cart handling option form that requires CSRF token

from os import environ
import transaction
import unittest
import webtest
import re

from ..models import get_tm_session
from ..models.meta import Base
from ..models.shop import Shop, get_shop_by_name
from ..models.user import get_or_create_user_by_email
from ..models.cart import get_all_carts
from ..models.product import get_all_products
from ..models.shop_location import ShopLocation
from ..models.price import Price

from pyramid.paster import get_appsettings


class PhysicalProductHandlingTests(unittest.TestCase):
    """Functional tests for physical product handling options with CSRF protection."""

    def setUp(self):
        from make_post_sell import main

        self.settings = get_appsettings("test.ini")
        self.app = main({}, **self.settings)
        self.testapp = webtest.TestApp(self.app)
        self.session_factory = self.app.registry["dbsession_factory"]
        self.engine = self.session_factory.kw["bind"]
        Base.metadata.create_all(bind=self.engine)
        self.dbsession = get_tm_session(self.session_factory, transaction.manager)

        # Create test user
        self.user = get_or_create_user_by_email(self.dbsession, "test@example.com")
        self.user_creds = ("test@example.com", self.user.new_password())
        self.dbsession.add(self.user)
        self.dbsession.flush()
        transaction.manager.commit()
        self.user = get_or_create_user_by_email(self.dbsession, "test@example.com")

    def tearDown(self):
        self.testapp.get("/log-out")
        transaction.abort()
        Base.metadata.drop_all(bind=self.engine)

    def log_in_user(self):
        self.testapp.post("/join-or-log-in", {"email": self.user_creds[0]})
        self.testapp.post(
            "/verification-challenge", {"raw-otp": self.user_creds[1], "submit": True}
        )

    def get_csrf_token(self, shop_id):
        """Extract CSRF token from search page."""
        url = f"/search?shop_id={shop_id}&keywords=test"
        res = self.testapp.get(url, status=[200, 302])
        if res.status_int == 302:
            res = res.follow()
        html = res.body.decode("utf-8")
        m = re.search(
            r'name=["\']csrf_token["\'].*?value=["\']([^"\']+)["\']',
            html,
            flags=re.IGNORECASE | re.DOTALL,
        )
        if not m:
            self.fail(f"CSRF token not found on {url}")
        return m.group(1)

    def test_physical_product_handling_option_selection(self):
        """Test selecting handling options for physical products with CSRF token.

        This is a regression test to ensure the handling option form includes
        CSRF token protection. Previously, the form was missing the CSRF token
        which caused "400 Bad CSRF Token" errors when users tried to select
        shipping options.
        """
        from ..models.cart import Cart
        from ..models.product import Product

        # 1. Create shop with user logged in
        self.log_in_user()

        shop = Shop(
            name="Test Physical Shop",
            phone_number="555-555-5555",
            billing_address="123 Test St\nTest City\n12345\n",
            description="Shop selling physical goods"
        )
        shop.stripe_public_api_key = environ["MPS_TEST_STRIPE_PUBLIC_API_KEY"]
        shop.stripe_secret_api_key = environ["MPS_TEST_STRIPE_SECRET_API_KEY"]
        shop.domain_name = "localhost.localhost"
        self.dbsession.add(shop)
        self.dbsession.flush()

        # 2. Create shop location with handling options enabled
        shop_location = ShopLocation(
            shop=shop,
            name="Main Location",
            address="123 Main St",
            city="Test City",
            state="TS",
            country="US",
            postal_code="12345",
        )
        # Enable all handling options with costs
        shop_location.local_pickup = True
        shop_location.local_delivery = True
        shop_location.local_delivery_rate_in_cents = 500  # $5
        shop_location.local_shipping = True
        shop_location.local_shipping_rate_in_cents = 750  # $7.50
        shop_location.international_shipping = True
        shop_location.international_shipping_rate_in_cents = 1500  # $15

        self.dbsession.add(shop_location)
        self.dbsession.flush()

        # 3. Create physical product
        product = Product(
            title="Physical Widget",
            description="A physical product that needs shipping"
        )
        product.shop_id = shop.id
        product.price_in_cents = 2000  # $20
        product.is_physical = True
        product.is_sellable = True
        self.dbsession.add(product)

        # Create price history for the product
        price = Price(product, 2000)
        self.dbsession.add(price)
        self.dbsession.flush()

        # 4. Create a cart with the physical product
        cart = Cart(user=self.user)
        cart.shop_id = shop.id
        cart.set_product_quantity(product, 1)
        self.dbsession.add(cart)
        self.dbsession.flush()

        # Save IDs before commit
        cart_id = cart.id
        cart_uuid_str = cart.uuid_str
        shop_uuid_str = shop.uuid_str

        # Commit transaction
        transaction.manager.commit()

        # Re-query cart to avoid detached instance error
        from ..models.cart import get_cart_by_id
        cart = get_cart_by_id(self.dbsession, cart_id)

        # Verify cart has physical products
        self.assertEqual(cart.count, 1)
        self.assertTrue(len(cart.physical_products) > 0, "Cart should have physical products")

        # 5. Get cart page and verify handling options section appears
        # Need to pass shop_id so request.shop is set correctly
        cart_res = self.testapp.get(f"/cart/{cart_id}?shop_id={shop_uuid_str}")
        cart_body = cart_res.body.decode()

        # Verify handling options form is present
        self.assertIn("Physical Handling Options", cart_body)
        self.assertIn("Local Pickup", cart_body)

        # 6. Submit handling option form with CSRF token
        # This is the critical test - the form MUST include CSRF token
        csrf_token = self.get_csrf_token(shop_uuid_str)

        handling_res = self.testapp.post(
            f"/cart/{cart_uuid_str}/handling-option",
            {
                "handling_option": "local_pickup",
                "csrf_token": csrf_token,
            },
        )

        # Should redirect back to cart page
        self.assertEqual(handling_res.status_int, 302)
        final_res = handling_res.follow()
        final_body = final_res.body.decode()

        # Verify success flash message appears
        self.assertIn("Handling option set successfully", final_body)

        # 7. Verify handling option was actually set in database
        self.dbsession.refresh(cart)
        self.assertEqual(cart.handling_option, "local_pickup")
        self.assertEqual(cart.handling_cost_in_cents, 0)  # Pickup is free

        print("✓ CSRF TOKEN TEST PASSED: Handling option form includes CSRF protection")
        print("✓ FORM SUBMISSION SUCCESSFUL: Handling option set without 400 error")

        # 8. Test other handling options to ensure they all work
        for option, expected_cost in [
            ("local_delivery", 500),
            ("local_shipping", 750),
            ("international_shipping", 1500),
        ]:
            csrf_token = self.get_csrf_token(shop_uuid_str)
            res = self.testapp.post(
                f"/cart/{cart_uuid_str}/handling-option",
                {
                    "handling_option": option,
                    "csrf_token": csrf_token,
                },
            )
            self.assertEqual(res.status_int, 302)
            self.assertIn("Handling option set successfully", res.follow().body.decode())

            self.dbsession.refresh(cart)
            self.assertEqual(cart.handling_option, option)
            self.assertEqual(cart.handling_cost_in_cents, expected_cost)

        print(f"✓ ALL HANDLING OPTIONS TESTED: local_pickup, local_delivery, local_shipping, international_shipping")
