import logging

from abc import (
    ABC,
    abstractmethod,
    abstractproperty
)
from typing import (
    List,
    Any
)
from dataclasses import dataclass
from django.http.request import HttpRequest
from django.conf import settings
from django.core import signing

from store.models import (
    Product,
    ProductAuthor,
    DeliveryMethod
)

logger = logging.getLogger("cart_logger")


class BaseCart(ABC):

    def validate_and_get_product(self, item_id):
        return Product.objects.get(id=item_id)

    @abstractmethod
    def add_item(self, item_id, quantity):
        ...
    
    @abstractmethod
    def remove_item(self, item_id):
        ...

    @abstractmethod
    def update_item_quantity(self, item_id, change):
        ...
    
    @abstractproperty
    def display_items(self):
        ...


class SessionCart(BaseCart):


    def _get_author_total_price(self, author_id: int):
        author_cart = self._cart[str(author_id)]
        author_price = 0
        product_ids = list(int(pk) for pk in author_cart.keys())
        queryset = Product.objects.filter(id__in=product_ids)
        for product in queryset:
            author_price += product.price * author_cart[str(product.id)]

        if self._delivery_info:
            author_price += self._delivery_info.price
        
        return author_price

    def _prepare_display_items(self)-> List[dict[str, dict|str]]:
        items: List[dict[str, dict|str]] = []
        for author_id, cart_items in self._cart.items():
            author = ProductAuthor.objects.get(id=int(author_id))
            products = []
            for item_id, quantity in cart_items.items():
                product=Product.objects.get(id=int(item_id))
                products.append({"product": product, "quantity": quantity})
            items.append({
                "author": author, 
                "products": products, 
                "group_price": self._get_author_total_price(author_id)
            })
        return items

    def __init__(self, request: HttpRequest, delivery: DeliveryMethod=None) -> None:
        super().__init__()
        self.session = request.session
        self._cart = self.session.get(settings.CART_SESSION_ID, None)
        if not self._cart:
            self._cart = {}
            self.session[settings.CART_SESSION_ID] = self._cart
        self._delivery_info = delivery
        self._display_items = self._prepare_display_items()
    
    def save_cart(self):
        self._display_items = self._prepare_display_items()
        self.session[settings.CART_SESSION_ID] = self._cart
        self.session.modified = True

    def add_item(self, item_id: int, quantity: int) -> None:
        product = self.validate_and_get_product(item_id)
        author = product.author
        quantity = int(quantity)
        item_id = int(item_id)
        if not self._cart.get(str(author.id)):
            self._cart[str(author.id)] = {str(item_id): quantity}
            self.save_cart()
        elif not self._cart[str(author.id)].get(str(item_id)):
            self._cart[str(author.id)].update({str(item_id): quantity})
            self.save_cart()
        else:
            new_quantity = self._cart[str(author.id)][str(item_id)] + quantity
            self.update_item_quantity(item_id, new_quantity)

    def remove_item(self, item_id: int) -> None:
        product = self.validate_and_get_product(item_id)
        author = product.author
        try:
            self._cart[str(author.id)].pop(str(item_id))
            self.save_cart()
        except KeyError:
            logger.exception(f"Item {item_id} not found in cart")
    
    def update_item_quantity(self, item_id: int, new_quantity: int) -> None:
        product = self.validate_and_get_product(item_id)
        author = product.author
        if new_quantity < 1:
            self.remove_item(item_id)
            return
        if not self._cart.get(str(author.id)):
            self.add_item(item_id, new_quantity)
            return
        self._cart[str(author.id)][str(product.id)] = new_quantity
        self.save_cart()

    @property
    def delivery_info(self):
        return self._delivery_info

    @property
    def display_items(self) -> List[dict[str, dict|str]]:
        return self._display_items
        
    @property
    def total_price(self):
        total = 0
        for _, cart_items in self._cart.items():
            for item_id, quantity in cart_items.items():
                product = Product.objects.get(id=int(item_id))
                total += product.price * quantity
        if self._delivery_info:
            total += self._delivery_info.price * len(self._cart.keys())
        return total
    
    def is_empty(self) -> bool:
        return not bool(self._cart.items())

    def clear(self) -> None:
        self._cart = {}
        self.save_cart()


class CustomerData:
    
    def _encrypt_data(self, data: dict[str, Any]) -> str:
        signer = signing.Signer()
        return signer.sign_object(data)

    def _decrypt_data(self, data: str) -> dict[str, Any]:
        signer = signing.Signer()
        return signer.unsign_object(data)

    def __init__(self, data: dict[str, Any]=None, encrypted_data: str=None) -> None:
        self._data = self._encrypt_data(data) if data else encrypted_data
    
    @property
    def data(self) -> dict[str, Any]:
        return self._data
    
    @property
    def decrypted_data(self) -> dict[str, Any]:
        return self._decrypt_data(self._data)
