# Copyright (C) 2020: Criptomart (https://criptomart.net)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.tests.common import TransactionCase


@tagged("post_install", "-at_install")
class TestProductTemplate(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        # Create a tax for the product
        cls.tax = cls.env["account.tax"].create(
            {
                "name": "Test Tax 21%",
                "amount": 21.0,
                "amount_type": "percent",
                "type_tax_use": "sale",
            }
        )

        # Create a pricelist with formula based on cost
        cls.pricelist = cls.env["product.pricelist"].create(
            {
                "name": "Test Pricelist Automatic",
                "currency_id": cls.env.company.currency_id.id,
            }
        )

        cls.pricelist_item = cls.env["product.pricelist.item"].create(
            {
                "pricelist_id": cls.pricelist.id,
                "compute_price": "formula",
                "base": "last_purchase_price",
                "price_discount": 0,
                "price_surcharge": 0,
                "price_markup": 50.0,  # 50% markup
                "applied_on": "3_global",
            }
        )

        # Set the pricelist in configuration
        cls.env["ir.config_parameter"].sudo().set_param(
            "product_sale_price_from_pricelist.product_pricelist_automatic",
            str(cls.pricelist.id),
        )

        # Create a product category
        cls.category = cls.env["product.category"].create(
            {
                "name": "Test Category",
            }
        )

        # Create a product
        cls.product = cls.env["product.template"].create(
            {
                "name": "Test Product",
                "categ_id": cls.category.id,
                "list_price": 10.0,
                "standard_price": 5.0,
                "taxes_id": [(6, 0, [cls.tax.id])],
            }
        )

        # Set price fields directly on variant to ensure they're set
        # (computed fields on template don't always propagate during create)
        cls.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": 5.0,
                "last_purchase_price_compute_type": "without_discounts",
            }
        )

    def _get_theoretical_price(self, product_template):
        """Helper to get theoretical price from variant, avoiding cache issues."""
        return product_template.product_variant_ids[:1].list_price_theoritical

    def _get_updated_flag(self, product_template):
        """Helper to get updated flag from variant, avoiding cache issues."""
        return product_template.product_variant_ids[:1].last_purchase_price_updated

    def test_compute_theoritical_price_without_tax_error(self):
        """Test that computing theoretical price without taxes raises an error"""
        product_no_tax = self.env["product.template"].create(
            {
                "name": "Product No Tax",
                "list_price": 10.0,
                "standard_price": 5.0,
                "taxes_id": [(5, 0, 0)],  # Clear all taxes
            }
        )
        # Write to variant directly
        product_no_tax.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": 5.0,
                "last_purchase_price_compute_type": "without_discounts",
                "taxes_id": [(5, 0, 0)],  # Ensure variant also has no taxes
            }
        )

        with self.assertRaises(UserError):
            product_no_tax._compute_theoritical_price()

    def test_compute_theoritical_price_success(self):
        """Test successful computation of theoretical price"""
        self.product._compute_theoritical_price()

        # Read from variant directly to avoid cache issues
        theoretical_price = self.product.product_variant_ids[:1].list_price_theoritical

        # Verify that theoretical price was calculated
        self.assertGreater(theoretical_price, 0)

        # Price is calculated WITHOUT tax (taxes added automatically on sales)
        # With 50% markup on 5.0 = 7.5
        self.assertAlmostEqual(theoretical_price, 7.50, places=2)

    def test_compute_theoritical_price_with_rounding(self):
        """Test that prices are properly calculated"""
        self.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": 4.0,
            }
        )
        self.product._compute_theoritical_price()

        # Price should be calculated (4.0 * 1.5 = 6.0)
        price = self._get_theoretical_price(self.product)
        self.assertAlmostEqual(price, 6.0, places=2)

    def test_last_purchase_price_updated_flag(self):
        """Test that the updated flag is set when prices differ"""
        initial_list_price = self.product.list_price
        self.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": 10.0,
            }
        )
        self.product._compute_theoritical_price()

        if self._get_theoretical_price(self.product) != initial_list_price:
            self.assertTrue(self._get_updated_flag(self.product))

    def test_action_update_list_price(self):
        """Test updating list price from theoretical price"""
        self.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": 8.0,
            }
        )
        self.product._compute_theoritical_price()

        theoretical_price = self._get_theoretical_price(self.product)
        self.product.action_update_list_price()

        # Verify that list price was updated
        self.assertEqual(self.product.list_price, theoretical_price)
        self.assertFalse(self._get_updated_flag(self.product))

    def test_manual_update_type_skips_automatic(self):
        """Test that manual update type prevents automatic price calculation"""
        self.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_compute_type": "manual_update",
            }
        )
        initial_list_price = self.product.list_price

        self.product.action_update_list_price()

        # List price should not change for manual update type
        self.assertEqual(self.product.list_price, initial_list_price)

    def test_price_compute_with_last_purchase_price(self):
        """Test price_compute method with last_purchase_price type"""
        # price_compute is defined on product.product, not product.template
        variant = self.product.product_variant_ids[:1]
        result = variant.price_compute("last_purchase_price")

        # Should return dummy prices (1.0) for all product ids
        for product_id in result:
            self.assertEqual(result[product_id], 1.0)

    def test_compute_theoritical_price_without_pricelist(self):
        """Test that missing pricelist raises an error"""
        # Remove pricelist configuration
        self.env["ir.config_parameter"].sudo().set_param(
            "product_sale_price_from_pricelist.product_pricelist_automatic", ""
        )

        with self.assertRaises(UserError):
            self.product._compute_theoritical_price()

        # Restore pricelist configuration for other tests
        self.env["ir.config_parameter"].sudo().set_param(
            "product_sale_price_from_pricelist.product_pricelist_automatic",
            str(self.pricelist.id),
        )

    def test_company_dependent_fields(self):
        """Test that price fields are company dependent"""
        # NOTE: company_dependent=True would require adding schema migration
        # to convert existing columns in production databases. These fields
        # use standard float/selection storage instead.

    def test_compute_theoritical_price_with_actual_purchase_price(self):
        """Test that theoretical price is calculated correctly from last purchase price
        This test simulates a real scenario where a product has a purchase price set"""
        # Set a realistic purchase price
        purchase_price = 10.50
        self.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": purchase_price,
                "last_purchase_price_compute_type": "without_discounts",
            }
        )

        # Compute theoretical price
        self.product._compute_theoritical_price()

        theoretical_price = self._get_theoretical_price(self.product)

        # Verify price is not zero
        self.assertNotEqual(
            theoretical_price,
            0.0,
            "Theoretical price should not be 0.0 when last_purchase_price_received is set",
        )

        # Verify price calculation is correct
        # Expected: 10.50 * 1.50 (50% markup) = 15.75
        # Price is WITHOUT tax (taxes added automatically on sales)
        expected_price = purchase_price * 1.50  # 15.75

        # Allow some tolerance for rounding
        self.assertAlmostEqual(
            theoretical_price,
            expected_price,
            delta=0.10,
            msg=f"Expected around {expected_price:.2f}, got {theoretical_price:.2f}",
        )

    def test_compute_price_zero_purchase_price(self):
        """Test behavior when last_purchase_price_received is 0.0"""
        self.product.product_variant_ids[:1].write(
            {
                "last_purchase_price_received": 0.0,
            }
        )
        self.product._compute_theoritical_price()

        # When purchase price is 0, theoretical price should also be 0
        self.assertEqual(
            self._get_theoretical_price(self.product),
            0.0,
            "Theoretical price should be 0.0 when last_purchase_price_received is 0.0",
        )

    def test_pricelist_item_base_field(self):
        """Test that pricelist item uses last_purchase_price as base"""
        self.assertEqual(
            self.pricelist_item.base,
            "last_purchase_price",
            "Pricelist item should use last_purchase_price as base",
        )

        # Verify the base field is properly configured
        self.assertEqual(self.pricelist_item.compute_price, "formula")
        self.assertEqual(self.pricelist_item.price_markup, 50.0)
