import json
from dataclasses import dataclass
from typing import Optional, Tuple
from uuid import uuid4

import requests
from django.apps import apps
from django.core.files.base import ContentFile
from django.db import models
from django.urls import reverse
from django_extensions.db.models import TimeStampedModel
from foods.sources.rscraper import RecipeScraperService
from foods.sources.usda import NutritionCalculator
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
from scrobbles.dataclasses import BaseLogData, WithPeopleLogData
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin

BNULL = {"blank": True, "null": True}


@dataclass
class FoodLogData(BaseLogData, WithPeopleLogData):
    calories: Optional[int] = None
    meal: Optional[str] = None


class FoodCategory(TimeStampedModel):
    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
    name = models.CharField(max_length=255)
    category_image = models.ImageField(upload_to="food/category/", **BNULL)
    category_image_small = ImageSpecField(
        source="category_image",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    category_image_medium = ImageSpecField(
        source="category_image",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    allrecipe_id = models.CharField(max_length=255, **BNULL)
    description = models.TextField(**BNULL)

    @classmethod
    def find_or_create(cls, title: str) -> "FoodCategory":
        return cls.objects.filter(title=title).first()

    def __str__(self):
        return self.name


class Food(ScrobblableMixin):
    # Recipe source tracking
    source_url = models.URLField(null=True, blank=True, unique=True)
    source_site = models.CharField(max_length=100, null=True, blank=True)

    # Recipe data
    description = models.TextField(**BNULL)  # Recipe title
    ingredients = models.TextField(**BNULL)  # JSON or newline-separated
    instructions = models.TextField(**BNULL)  # JSON or newline-separated
    prep_time_minutes = models.IntegerField(**BNULL)
    cook_time_minutes = models.IntegerField(**BNULL)
    total_time_minutes = models.IntegerField(**BNULL)
    servings = models.IntegerField(**BNULL)
    yield_text = models.CharField(max_length=255, **BNULL)  # e.g., "8 calzones"

    # Nutrition (per serving)
    calories = models.IntegerField(**BNULL)
    protein = models.DecimalField(max_digits=10, decimal_places=2, **BNULL)
    fat = models.DecimalField(max_digits=10, decimal_places=2, **BNULL)
    carbohydrates = models.DecimalField(max_digits=10, decimal_places=2, **BNULL)
    fiber = models.DecimalField(max_digits=10, decimal_places=2, **BNULL)
    sugar = models.DecimalField(max_digits=10, decimal_places=2, **BNULL)
    sodium = models.DecimalField(max_digits=10, decimal_places=2, **BNULL)

    recipe_image = models.ImageField(upload_to="food/recipe/", **BNULL)
    recipe_image_small = ImageSpecField(
        source="recipe_image",
        processors=[ResizeToFit(100, 100)],
        format="JPEG",
        options={"quality": 60},
    )
    recipe_image_medium = ImageSpecField(
        source="recipe_image",
        processors=[ResizeToFit(300, 300)],
        format="JPEG",
        options={"quality": 75},
    )
    allrecipe_id = models.CharField(max_length=255, **BNULL)
    allrecipe_rating = models.FloatField(**BNULL)
    off_code = models.CharField(max_length=255, unique=True, **BNULL)
    category = models.ForeignKey(FoodCategory, on_delete=models.DO_NOTHING, **BNULL)

    class Meta:
        indexes = [
            models.Index(fields=["source_url"]),
            models.Index(fields=["allrecipe_id"]),
            models.Index(fields=["off_code"]),
            models.Index(fields=["description"]),
        ]

    def get_absolute_url(self) -> str:
        return reverse("foods:food_detail", kwargs={"slug": self.uuid})

    @property
    def subtitle(self):
        if self.category:
            return self.category.name

    @property
    def strings(self) -> ScrobblableConstants:
        return ScrobblableConstants(verb="Eating", tags="food")

    @property
    def allrecipe_link(self) -> str:
        link = ""
        if self.producer and self.allrecipe_id:
            if self.allrecipe_id:
                link = f"https://www.allrecipe.com/{self.allrecipe_id}/"
        return link

    @property
    def primary_image_url(self) -> str:
        url = ""
        if self.recipe_image:
            url = self.recipe_image.url
        return url

    @property
    def logdata_cls(self):
        return FoodLogData

    @classmethod
    def find_or_create_from_recipe(cls, url: str, category=None) -> Tuple["Food", bool]:
        """
        Scrape a recipe URL and create/update Food instance.
        Uses django-taggit for tags and existing FoodCategory for category.
        """
        # Check if URL already exists
        existing = cls.objects.filter(source_url=url).first()
        if existing:
            return existing, False

        # Scrape the recipe
        scraper = RecipeScraperService()
        recipe_data = scraper.scrape_url(url)

        # Download image
        recipe_image = None
        if recipe_data.get("image"):
            recipe_image = scraper.download_image(recipe_data["image"])

        # Extract tags
        tags = scraper.extract_tags(recipe_data)

        # Calculate nutrition
        nutrition = recipe_data.get("nutrition")
        if not nutrition:
            calculator = NutritionCalculator()
            servings = scraper.parse_servings(recipe_data.get("yields", "1")) or 1
            nutrition = calculator.calculate_nutrition(
                recipe_data.get("ingredients", []), servings
            )
        else:
            servings = scraper.parse_servings(recipe_data.get("yields", "1")) or 1

        # Get or create category (if not provided)
        if not category and recipe_data.get("category"):
            category, _ = FoodCategory.objects.get_or_create(
                name=recipe_data["category"]
            )

        # Create Food instance
        food = cls.objects.create(
            title=recipe_data.get("title"),
            source_url=url,
            source_site=recipe_data.get("site"),
            ingredients=json.dumps(recipe_data.get("ingredients", [])),
            instructions=json.dumps(recipe_data.get("instructions", [])),
            prep_time_minutes=recipe_data.get("prep_time"),
            cook_time_minutes=recipe_data.get("cook_time"),
            total_time_minutes=recipe_data.get("total_time"),
            servings=servings,
            yield_text=recipe_data.get("yields"),
            # cuisine=recipe_data.get("cuisine"),
            # course=recipe_data.get("course"),
            calories=int(nutrition.get("calories", 0)) if nutrition else None,
            protein=nutrition.get("protein") if nutrition else None,
            fat=nutrition.get("fat") if nutrition else None,
            carbohydrates=(nutrition.get("carbohydrates") if nutrition else None),
            fiber=nutrition.get("fiber") if nutrition else None,
            category=category,
        )

        # Add tags via django-taggit
        if tags:
            food.genre.add(*tags)

        # Save image if downloaded
        if recipe_image:
            food.recipe_image.save(recipe_image.name, recipe_image, save=True)

        if "allrecipes.com" in url:
            food.allrecipe_id = url.split("/recipe/")[-1].split("/")[0]
            food.save()

        return food, True

    @classmethod
    def find_or_create_from_ingredients(
        cls,
        ingredients: list[str],
        title: str,
        servings: int = 1,
        category=None,
    ) -> Tuple["Food", bool]:
        """
        Create Food from ingredient list (no URL).
        Calculates nutrition using USDA API.
        """
        # Check if similar recipe exists
        existing = cls.objects.filter(description__icontains=title).first()
        if existing:
            return existing, False

        calculator = NutritionCalculator()
        nutrition = calculator.calculate_nutrition(ingredients, servings)

        food = cls.objects.create(
            description=title,
            ingredients=json.dumps(ingredients),
            servings=servings,
            calories=int(nutrition.get("calories", 0)) if nutrition else None,
            protein=nutrition.get("protein") if nutrition else None,
            fat=nutrition.get("fat") if nutrition else None,
            carbohydrates=(nutrition.get("carbohydrates") if nutrition else None),
            fiber=nutrition.get("fiber") if nutrition else None,
            category=category,
        )

        return food, True

    @classmethod
    def find_or_create_from_search(cls, product_data: dict) -> "Food":
        code = product_data.get("code", "")
        product_name = product_data.get("product_name", "")
        brands = product_data.get("brands", "")

        food = None
        if code:
            food = cls.objects.filter(off_code=code).first()
        if not food and product_name:
            food = cls.objects.filter(
                title=product_name, source_site="Open Food Facts"
            ).first()

        if food:
            return food

        nutriments = product_data.get("nutriments", {})

        category = None
        categories = product_data.get("categories", [])
        if categories:
            category, _ = FoodCategory.objects.get_or_create(name=categories[0])

        food = cls.objects.create(
            title=product_name,
            description=f"{brands} - {product_name}" if brands else product_name,
            source_site="Open Food Facts",
            ingredients=product_data.get("ingredients_text", ""),
            calories=nutriments.get("calories"),
            protein=nutriments.get("protein"),
            fat=nutriments.get("fat"),
            carbohydrates=nutriments.get("carbohydrates"),
            fiber=nutriments.get("fiber"),
            sugar=nutriments.get("sugar"),
            sodium=nutriments.get("sodium"),
            off_code=code,
            category=category,
        )

        image_url = product_data.get("image_url", "")
        if image_url:
            food.save_image_from_url(image_url)

        return food

    def save_image_from_url(self, url: str):
        headers = {"User-Agent": "Vrobbler/1.0"}
        try:
            r = requests.get(url, headers=headers, timeout=10)
            if r.status_code == 200:
                fname = f"{self.title}_{self.uuid}.jpg"
                self.recipe_image.save(fname, ContentFile(r.content), save=True)
        except requests.RequestException:
            pass

    def refresh_nutrition(self) -> bool:
        """Recalculate nutrition from ingredients (useful if USDA data updated)."""
        if not self.ingredients:
            return False

        try:
            ingredients = json.loads(self.ingredients)
            calculator = NutritionCalculator()
            nutrition = calculator.calculate_nutrition(ingredients, self.servings or 1)

            self.calories = int(nutrition.get("calories", 0))
            self.protein = nutrition.get("protein")
            self.fat = nutrition.get("fat")
            self.carbohydrates = nutrition.get("carbohydrates")
            self.fiber = nutrition.get("fiber")
            self.save(
                update_fields=[
                    "calories",
                    "protein",
                    "fat",
                    "carbohydrates",
                    "fiber",
                ]
            )

            return True
        except Exception:
            return False

    def scrobbles(self, user_id):
        Scrobble = apps.get_model("scrobbles", "Scrobble")
        return Scrobble.objects.filter(user_id=user_id, food=self).order_by(
            "-timestamp"
        )
