from pathlib import Path

from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Count

from market.models import (
    FarmerMarketListing,
    FarmerMarketListingImage,
    MARKET_PRODUCT_IMAGE_LIMIT,
    MarketProduct,
    MarketProductImage,
)


class Command(BaseCommand):
    help = (
        "Populate marketplace seller products and farmer products that are missing "
        "images with the default farm_produce fallback image."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--target",
            choices=["all", "products", "farmer-listings"],
            default="all",
            help="Which marketplace records to populate. Defaults to all public marketplace product types.",
        )
        parser.add_argument(
            "--max-images",
            type=int,
            default=1,
            help=(
                "Target number of fallback images per item. Defaults to 1 so the "
                "default card/detail image appears without filling the whole gallery."
            ),
        )
        parser.add_argument(
            "--fill-gallery",
            action="store_true",
            help=f"Fill each item's gallery up to the {MARKET_PRODUCT_IMAGE_LIMIT}-image limit.",
        )
        parser.add_argument(
            "--image-path",
            default="",
            help="Optional fallback image path. Defaults to static/img/farm_produce.jpg or .jfif.",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would be changed without creating image records.",
        )

    def handle(self, *args, **options):
        max_images = MARKET_PRODUCT_IMAGE_LIMIT if options["fill_gallery"] else int(options["max_images"] or 1)
        if max_images < 1 or max_images > MARKET_PRODUCT_IMAGE_LIMIT:
            raise CommandError(f"--max-images must be between 1 and {MARKET_PRODUCT_IMAGE_LIMIT}.")

        fallback_path = self._find_fallback_image(options.get("image_path") or "")
        if not fallback_path:
            raise CommandError(
                "Fallback image not found. Expected static/img/farm_produce.jpg or static/img/farm_produce.jfif."
            )

        fallback_content = fallback_path.read_bytes()
        self.stdout.write(self.style.SUCCESS(f"Using fallback image from: {fallback_path}"))
        self.stdout.write(f"Fallback image size: {len(fallback_content)} bytes")
        self.stdout.write(f"Target images per item: {max_images}")
        if options["dry_run"]:
            self.stdout.write(self.style.WARNING("Dry run only. No image records will be created."))
        self.stdout.write("")

        target = options["target"]
        total_added = 0

        if target in {"all", "products"}:
            total_added += self._populate_market_products(
                fallback_content=fallback_content,
                fallback_path=fallback_path,
                max_images=max_images,
                dry_run=options["dry_run"],
            )

        if target in {"all", "farmer-listings"}:
            total_added += self._populate_farmer_listings(
                fallback_content=fallback_content,
                fallback_path=fallback_path,
                max_images=max_images,
                dry_run=options["dry_run"],
            )

        action = "Would add" if options["dry_run"] else "Added"
        self.stdout.write("")
        self.stdout.write(self.style.SUCCESS(f"Complete. {action} {total_added} total image(s)."))

    def _populate_market_products(self, *, fallback_content, fallback_path, max_images, dry_run):
        products = (
            MarketProduct.objects.filter(
                status=MarketProduct.Status.APPROVED,
                is_active=True,
            )
            .annotate(image_count=Count("images"))
            .order_by("category__name", "name", "id")
        )
        self.stdout.write(f"Processing {products.count()} approved seller product(s)...")
        return self._populate_items(
            queryset=products,
            relation_name="product",
            image_model=MarketProductImage,
            filename_prefix="product",
            fallback_content=fallback_content,
            fallback_path=fallback_path,
            max_images=max_images,
            dry_run=dry_run,
        )

    def _populate_farmer_listings(self, *, fallback_content, fallback_path, max_images, dry_run):
        listings = (
            FarmerMarketListing.objects.filter(
                status__in=[
                    FarmerMarketListing.Status.LIVE,
                    FarmerMarketListing.Status.EXPIRED,
                ]
            )
            .annotate(image_count=Count("images"))
            .order_by("category__name", "name", "id")
        )
        self.stdout.write(f"Processing {listings.count()} public farmer product(s)...")
        return self._populate_items(
            queryset=listings,
            relation_name="listing",
            image_model=FarmerMarketListingImage,
            filename_prefix="farmer-listing",
            fallback_content=fallback_content,
            fallback_path=fallback_path,
            max_images=max_images,
            dry_run=dry_run,
        )

    def _populate_items(
        self,
        *,
        queryset,
        relation_name,
        image_model,
        filename_prefix,
        fallback_content,
        fallback_path,
        max_images,
        dry_run,
    ):
        added_count = 0
        changed_items = 0
        skipped_items = 0

        for item in queryset:
            current_count = int(getattr(item, "image_count", 0) or 0)
            if current_count >= max_images:
                skipped_items += 1
                continue

            images_to_add = max_images - current_count
            changed_items += 1
            self.stdout.write(
                f"- {item.name} (id={item.id}): "
                f"{current_count} image(s), {'would add' if dry_run else 'adding'} {images_to_add}."
            )

            if dry_run:
                added_count += images_to_add
                continue

            for sort_order in range(current_count, max_images):
                upload = ContentFile(
                    fallback_content,
                    name=self._fallback_filename(
                        prefix=filename_prefix,
                        item_id=item.id,
                        sort_order=sort_order,
                        fallback_path=fallback_path,
                    ),
                )
                image_model.objects.create(
                    **{relation_name: item},
                    image=upload,
                    alt_text=(getattr(item, "name", "") or "Marketplace product")[:255],
                    caption="Default marketplace image",
                    is_primary=current_count == 0 and sort_order == 0,
                    sort_order=sort_order,
                )
                added_count += 1

        self.stdout.write(
            f"Changed {changed_items} item(s), skipped {skipped_items} item(s) that already met the target."
        )
        return added_count

    def _find_fallback_image(self, explicit_path):
        if explicit_path:
            path = Path(explicit_path)
            return path if path.exists() else None

        project_root = Path(__file__).resolve().parent.parent.parent.parent
        candidates = [
            project_root / "static" / "img" / "farm_produce.jpg",
            project_root / "static" / "img" / "farm_produce.jfif",
            Path("static/img/farm_produce.jpg"),
            Path("static/img/farm_produce.jfif"),
        ]

        for path in candidates:
            if path.exists():
                return path
        return None

    def _fallback_filename(self, *, prefix, item_id, sort_order, fallback_path):
        suffix = fallback_path.suffix or ".jpg"
        return f"{prefix}-{item_id}-fallback-{sort_order}{suffix}"
