from django.db import models
from django.conf import settings
from django.utils import timezone
from django.core.exceptions import ValidationError
from decimal import Decimal
from django.template.loader import render_to_string
from django.core.files.base import ContentFile
from django.core.validators import MaxValueValidator, MinValueValidator

from weasyprint import HTML
import os

from configuration.models import ShopConfiguration
from thirdparty.zasilkovna.models import ZasilkovnaPacket
from thirdparty.stripe.models import StripeModel

#FIXME: přidat soft delete pro všchny modely !!!!

class Category(models.Model):
    name = models.CharField(max_length=100)

    #adresa kategorie např: /category/elektronika/mobily/
    url = models.SlugField(unique=True)

    #kategorie se můžou skládat pod sebe
    parent = models.ForeignKey(
        'self', null=True, blank=True, on_delete=models.CASCADE, related_name='subcategories'
    )

    description = models.TextField(blank=True)

    #ikona
    image = models.ImageField(upload_to='categories/', blank=True) 

    class Meta:
        verbose_name_plural = "Categories"

    def __str__(self):
        return self.name
    


class Product(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)

    code = models.CharField(max_length=100, unique=True, blank=True, null=True)

    variants = models.ManyToManyField(
        "self",
        symmetrical=True,
        blank=True,
        related_name="variant_of",
        help_text=(
            "Symetrické varianty produktu: pokud přidáte variantu A → B, "
            "Django automaticky přidá i variantu B → A. "
            "Všechny varianty jsou rovnocenné a zobrazí se vzájemně."
        ),
    )

    category = models.ForeignKey(Category, related_name='products', on_delete=models.PROTECT)

    price = models.DecimalField(max_digits=10, decimal_places=2)

    url = models.SlugField(unique=True)

    stock = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)

    #časový limit (volitelné)
    limited_to = models.DateTimeField(null=True, blank=True)

    default_carrier = models.ForeignKey(
        "Carrier", on_delete=models.SET_NULL, null=True, blank=True, related_name="default_for_products"
    )

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    @property
    def available(self):
        return self.is_active and self.stock > 0

    def __str__(self):
        return f"{self.name} ({self.price} {self.currency.upper()})"
    
#obrázek pro produkty
class ProductImage(models.Model):
    product = models.ForeignKey(Product, related_name='images', on_delete=models.CASCADE)

    image = models.ImageField(upload_to='products/')

    alt_text = models.CharField(max_length=150, blank=True)
    is_main = models.BooleanField(default=False)

    def __str__(self):
        return f"{self.product.name} image"



# ------------------ OBJEDNÁVKY ------------------

class Order(models.Model):
    class Status(models.TextChoices):
        CREATED = "created", "Vytvořeno"
        CANCELLED = "cancelled", "Zrušeno"
        COMPLETED = "completed", "Dokončeno"

        REFUNDING = "refunding", "Vrácení v procesu"
        REFUNDED = "refunded", "Vráceno"

    status = models.CharField(
        max_length=20, choices=Status.choices, null=True, blank=True, default=Status.CREATED
    )

    # Stored order grand total; recalculated on save
    total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    currency = models.CharField(max_length=10, default="CZK")

    # fakturační údaje (zkopírované z user profilu při objednávce)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="orders", null=True, blank=True
    )

    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    email = models.EmailField()
    phone = models.CharField(max_length=20, blank=True)
    address = models.CharField(max_length=255)
    city = models.CharField(max_length=100)
    postal_code = models.CharField(max_length=20)
    country = models.CharField(max_length=100, default="Czech Republic")

    note = models.TextField(blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    carrier = models.OneToOneField(
        "Carrier",
        on_delete=models.CASCADE,
        related_name="orders",
        null=True,
        blank=True
    )

    payment = models.OneToOneField(
        "Payment",
        on_delete=models.CASCADE,
        related_name="orders",
        null=True,
        blank=True
    )

    invoice = models.OneToOneField("Invoice", on_delete=models.CASCADE, related_name="order", null=True, blank=True)

    discount = models.ManyToManyField("DiscountCode", blank=True, related_name="orders")
    
    def calculate_total_price(self):
        carrier_price = self.carrier.get_price() if self.carrier else Decimal("0.0")

        if self.discount.exists():
            discounts = list(self.discount.all())

            total = Decimal('0.0')

            # getting all prices from order items (with discount applied if valid)
            for item in self.items.all():
                total = total + item.get_total_price(discounts)

            return total + carrier_price
        
        else:
            total = Decimal('0.0')
            # getting all prices from order items (without discount)

            for item in self.items.all():
                total = total + (item.product.price * item.quantity)

            return total + carrier_price

    def import_data_from_user(self):
        """Import user data into order for billing purposes."""
        self.first_name = self.user.first_name
        self.last_name = self.user.last_name
        self.email = self.user.email
        self.phone = self.user.phone
        self.address = f"{self.user.street} {self.user.street_number}"
        self.city = self.user.city
        self.postal_code = self.user.postal_code
        self.country = self.user.country

    def save(self, *args, **kwargs):
        # Keep total_price always in sync with items and discount
        self.total_price = self.calculate_total_price()

        if self.user and self.pk is None:
            self.import_data_from_user()
        
        super().save(*args, **kwargs)


# ------------------ DOPRAVCI A ZPŮSOBY DOPRAVY ------------------

class Carrier(models.Model):
    class SHIPPING(models.TextChoices):
        ZASILKOVNA = "packeta", "Zásilkovna"
        STORE = "store", "Osobní odběr"
    shipping_method = models.CharField(max_length=20, choices=SHIPPING.choices, default=SHIPPING.STORE)

    class STATE(models.TextChoices):
        PREPARING = "ordered", "Objednávka se připravuje"
        SHIPPED = "shipped", "Odesláno"
        DELIVERED = "delivered", "Doručeno"
        READY_TO_PICKUP = "ready_to_pickup", "Připraveno k vyzvednutí"
        #RETURNING = "returning", "Vracení objednávky"
    state = models.CharField(max_length=20, choices=STATE.choices, default=STATE.PREPARING)

    # prodejce to přidá později 
    zasilkovna = models.ManyToManyField(
        ZasilkovnaPacket, blank=True, related_name="carriers"
    )

    weight = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, help_text="Hmotnost zásilky v kg")

    returning = models.BooleanField(default=False, help_text="Zda je tato zásilka na vrácení")
    
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

    def get_price(self):
        if self.shipping_method == self.SHIPPING.ZASILKOVNA:
            return ShopConfiguration.get_solo().zasilkovna_shipping_price
        else:
            return Decimal('0.0')
        
    
    #tohle bude vyvoláno pomocí admina přes api!!!
    def start_ordering_shipping(self):
        if self.shipping_method == self.SHIPPING.ZASILKOVNA:
            # Uživatel může objednat více zásilek pokud potřeba
            self.zasilkovna.add(ZasilkovnaPacket.objects.create())
            self.returning = False
            self.save()

        else:
            raise ValidationError("Tato metoda dopravy nepodporuje objednání přepravy.")

        #... další logika pro jiné způsoby dopravy
        #TODO: přidat notifikace uživateli, jak pro zásilkovnu, tak pro vyzvednutí v obchodě!

    def ready_to_pickup(self):
        if self.shipping_method == self.SHIPPING.STORE:
            self.state = self.STATE.READY_TO_PICKUP
            self.save()
        else:
            raise ValidationError("Tato metoda dopravy nepodporuje připravení k vyzvednutí.")

    # def returning_shipping(self, int:id):
    #     self.returning = True

    #     if self.shipping_method == self.SHIPPING.ZASILKOVNA:
    #         #volá se na api Zásilkovny
    #         self.zasilkovna.get(id=id).returning_packet()


# ------------------ PLATEBNÍ MODELY ------------------

class Payment(models.Model):
    class PAYMENT(models.TextChoices):
        SHOP = "shop", "Platba v obchodě"
        STRIPE = "stripe", "Bankovní převod"
        CASH_ON_DELIVERY = "cash_on_delivery", "Dobírka"
    payment_method = models.CharField(max_length=30, choices=PAYMENT.choices, default=PAYMENT.SHOP)

    #FIXME: potvrdit že logika platby funguje správně
    #veškera logika a interakce bude na stripu (třeba aktualizovaní objednávky na zaplacenou apod.)
    stripe = models.OneToOneField(
        StripeModel, on_delete=models.CASCADE, null=True, blank=True, related_name="payment"
    )

    def save(self, *args, **kwargs):
        
        if self.order:
            order = Order.objects.get(payment=self)

            #validace platebních metod
            if self.payment_method == self.PAYMENT.SHOP and Carrier.objects.get(orders=order).shipping_method != Carrier.SHIPPING.STORE:
                raise ValueError("Platba v obchodě je možná pouze pro osobní odběr.")

            #validace dobírky (jestli není použitá pro osobní odběr)
            elif self.payment_method == self.PAYMENT.CASH_ON_DELIVERY and Carrier.objects.get(orders=order).shipping_method == Carrier.SHIPPING.STORE:
                raise ValueError("Dobírka není možná pro osobní odběr.")

            #vytvoření platebních metod pokud nový objekt
            if not self.pk:
                if self.payment_method == self.PAYMENT.STRIPE:
                    self.stripe = StripePayment.objects.create(amount=order.total_price)
                else:
                    self.stripe = None


        super().save(*args, **kwargs)


# ------------------ SLEVOVÉ KÓDY ------------------

class DiscountCode(models.Model):
    code = models.CharField(max_length=50, unique=True)
    description = models.CharField(max_length=255, blank=True)
    
    # sleva v procentech (0–100)
    percent = models.PositiveIntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(100)], 
        help_text="Procento sleva 0-100", 
        null=True, 
        blank=True
    )

    # nebo fixní částka
    amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, help_text="Fixní sleva v CZK")

    valid_from = models.DateTimeField(default=timezone.now)
    valid_to = models.DateTimeField(null=True, blank=True)
    active = models.BooleanField(default=True)

    #max počet použití
    usage_limit = models.PositiveIntegerField(null=True, blank=True)
    used_count = models.PositiveIntegerField(default=0)


    specific_products = models.ManyToManyField(
        Product, blank=True, related_name="discount_codes"
    )
    specific_categories = models.ManyToManyField(
        Category, blank=True, related_name="discount_codes"
    )

    def is_valid(self):
        now = timezone.now()
        if not self.active:
            return False
        
        if self.valid_to and self.valid_to < now:
            return False
        
        if self.usage_limit and self.used_count >= self.usage_limit:
            return False

        return True

    def __str__(self):
        return f"{self.code} ({self.percent}% or {self.amount} CZK)"
    

# ------------------ OBJEDNANÉ POLOŽKY ------------------

class OrderItem(models.Model):
    order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE)
    product = models.ForeignKey("products.Product", on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField(default=1)

    def get_total_price(self, discounts: list[DiscountCode] = None):
        """Vrátí celkovou cenu položky po aplikaci relevantních kupónů.

        Logika dle ShopConfiguration:
        - multiplying_coupons=True: procentuální slevy se násobí (sekvenčně)
          P * (1 - p1) -> výsledné * (1 - p2) ...
          jinak se použije pouze nejlepší (nejvyšší procento).
        - addition_of_coupons_amount=True: fixní částky (amount) se sčítají,
          jinak se použije pouze nejvyšší částka.
        - Kombinace: nejprve procentuální část, poté odečtení fixní částky.
        - Sleva se nikdy nesmí dostat pod 0.
        """
        base_price = self.product.price * self.quantity

        if not discounts:
            return base_price


        config = ShopConfiguration.get_solo()

        #seznám slev
        applicable_percent_discounts: list[int] = []
        applicable_amount_discounts: list[Decimal] = []

        #procházení kupónů a určení, které se aplikují
        for discount in set(discounts):
            if not discount:
                continue

            if not discount.is_valid():
                raise ValueError("Invalid discount code.")

            #defaulting
            applies = False

            # Určení, zda kupon platí pro produkt/kategorii
            # prázdný produkt a kategorie = globální kupon
            if discount.specific_products.exists() or discount.specific_categories.exists():
                if (self.product in discount.specific_products.all() or self.product.category in discount.specific_categories.all()):
                    applies = True

            else:
                applies = True #global

            if not applies:
                continue

            if discount.percent is not None:
                applicable_percent_discounts.append(discount.percent)
            elif discount.amount is not None:
                applicable_amount_discounts.append(discount.amount)

        final_price = base_price

        # Procentuální slevy
        if applicable_percent_discounts:

            if config.multiplying_coupons:
                for pct in applicable_percent_discounts:
                    factor = (Decimal('1') - (Decimal(pct) / Decimal('100')))
                    final_price = final_price * factor
            else:
                best_pct = max(applicable_percent_discounts)
                factor = (Decimal('1') - (Decimal(best_pct) / Decimal('100')))
                final_price = final_price * factor

        # Fixní částky
        if applicable_amount_discounts:
            if config.addition_of_coupons_amount:
                total_amount = sum(applicable_amount_discounts)

            else:
                total_amount = max(applicable_amount_discounts)
                
            final_price = final_price - total_amount

        if final_price < Decimal('0'):
            final_price = Decimal('0')

        return final_price.quantize(Decimal('0.01'))

    def __str__(self):
        return f"{self.product.name} x{self.quantity}"
    
    def save(self, *args, **kwargs):
        if self.pk is None:
            if self.order.payment.payment_method:
                raise ValueError("Nelze upravit položky z objednávky s již zvolenou platební metodou.")
            
            else:
                #nová položka objednávky, snížit skladové zásoby
                if self.product.stock < self.quantity:
                    raise ValueError("Nedostatečný skladový zásob pro produkt.")

                self.product.stock -= self.quantity
                self.product.save(update_fields=["stock"])

        super().save(*args, **kwargs)
    

class Refund(models.Model):
    order = models.ForeignKey(Order, related_name="refunds", on_delete=models.CASCADE)

    class Reason(models.TextChoices):
        RETUNING_PERIOD = "retuning_before_fourteen_day_period", "Vrácení před uplynutím 14-ti denní lhůty"
        DAMAGED_PRODUCT = "damaged_product", "Poškozený produkt"
        WRONG_ITEM = "wrong_item", "Špatná položka"
        OTHER = "other", "Jiný důvod"
    reason_choice = models.CharField(max_length=30, choices=Reason.choices)
    reason_text = models.TextField(blank=True)

    verified = models.BooleanField(default=False)

    created_at = models.DateTimeField(auto_now_add=True)

    #VRACENÍ ZÁSILKY, LOGIKA (DISABLED FOR NOW)
    # def save(self, *args, **kwargs):
    #     # Automaticky aktualizovat stav objednávky na "vráceno"
    #     if self.pk is None:
    #         self.order.status = Order.Status.REFUNDING
    #         self.order.save(update_fields=["status", "updated_at"])

    #         shipping_method = self.order.carrier.shipping_method

    #         if shipping_method == Carrier.SHIPPING.ZASILKOVNA:

    #             carrier = self.order.carrier;

    #             # poslední odeslána/vytvořená zásilka                
    #             # Iniciovat vrácení přes Zásilkovnu
    #             carrier.zasilkovna.latest('created_at').returning_packet()
    #             carrier.save()
                
    #         else:
    #             # Logika pro jiné způsoby dopravy
    #             pass

    #     super().save(*args, **kwargs)

    def refund_completed(self):
        # Aktualizovat stav objednávky na "vráceno"
        self.order.payment.stripe.refund()
        self.order.status = Order.Status.REFUNDED
        self.order.save(update_fields=["status", "updated_at"])

    def generate_refund_pdf_for_customer(self):
        """Vygeneruje PDF formulář k vrácení zboží pro zákazníka.

        Šablona refund/customer_in_package_returning_form.html očekává:
        - order: objekt objednávky
        - items: seznam položek (dict) s klíči product_name, sku, quantity, variant, options, reason
        - return_reason: textový důvod vrácení (kombinace reason_text / reason_choice)

        Návratová hodnota: bytes (PDF obsah). Uložení necháváme na volající logice.
        """
        order = self.order

        # Připravíme položky pro šablonu (důvody per položku zatím None – lze rozšířit)
        prepared_items: list[dict] = []
        for item in order.items.select_related('product'):
            prepared_items.append({
                "product_name": getattr(item.product, "name", "Item"),
                "name": getattr(item.product, "name", "Item"),  # fallbacky pro různé názvy v šabloně
                "sku": getattr(item.product, "code", None),
                "quantity": item.quantity,
                "variant": None,   # lze doplnit pokud existují varianty
                "options": None,   # lze doplnit pokud existují volby
                "reason": None,    # per-item reason (zatím nepodporováno)
            })

        return_reason = self.reason_text or self.get_reason_choice_display()

        context = {
            "order": order,
            "items": prepared_items,
            "return_reason": return_reason,
        }

        html_string = render_to_string("refund/customer_in_package_returning_form.html", context)
        pdf_bytes = HTML(string=html_string).write_pdf()
        return pdf_bytes


class Invoice(models.Model):
    invoice_number = models.CharField(max_length=50, unique=True)

    issued_at = models.DateTimeField(auto_now_add=True)
    due_date = models.DateTimeField()

    pdf_file = models.FileField(upload_to='invoices/')

    def __str__(self):
        return f"Invoice {self.invoice_number} for Order {self.order.id}"
    
    def generate_invoice_pdf(self):
        order = Order.objects.get(invoice=self)
        # Render HTML
        html_string = render_to_string("invoice/invoice.html", {"invoice": self})
        pdf_bytes = HTML(string=html_string).write_pdf()

        # Save directly to FileField
        self.pdf_file.save(f"{self.invoice_number}.pdf", ContentFile(pdf_bytes))
        self.save()