# extractly/management/commands/deactivate_inactive_ads.py
from __future__ import annotations

from django.core.management.base import BaseCommand, CommandError
from django.apps import apps
from django.db.models import Q
from django.core.exceptions import FieldDoesNotExist
from django.utils.timezone import now
from datetime import timedelta
import json

# ⬇️ dodatkowe importy do trybu 'set-check-active-by-source'
from extractly.models import AdsManual, SourceNetwork, SourceHtml


def _has_field(model, name: str) -> bool:
    try:
        model._meta.get_field(name)
        return True
    except FieldDoesNotExist:
        return False


def _active_flag_field(model) -> str | None:
    if _has_field(model, "is_active"):
        return "is_active"
    return None


def _eligible_models():
    for model in apps.get_models():
        if _has_field(model, "inactive_date") and _active_flag_field(model):
            yield model


def _resolve_model(label: str):
    try:
        return apps.get_model(label)
    except (LookupError, ValueError) as e:
        raise CommandError(f"Cannot resolve model '{label}': {e}")


def _resolve_source_ids_by_name_like(name_like: str):
    """
    Zwraca set UUID-ów SourceNetwork pasujących po:
      - SourceHtml.name ~ name_like
      - SourceNetwork.title ~ name_like
      - SourceNetwork.name ~ name_like
    (case-insensitive, partial).
    """
    name_like = (name_like or "").strip()
    if not name_like:
        return set()

    src_ids = set()

    # po SourceHtml.name
    for sh in SourceHtml.objects.filter(name__icontains=name_like).select_related("source"):
        if sh.source_id:
            src_ids.add(sh.source_id)

    # po SourceNetwork.title / name
    for sn in SourceNetwork.objects.filter(
        Q(title__icontains=name_like) | Q(name__icontains=name_like)
    ).only("id"):
        src_ids.add(sn.id)

    return src_ids


class Command(BaseCommand):
    help = (
        "Two-in-one:\n"
        "1) Deactivate by inactive_date (default flow).\n"
        "2) Set check_active=True for all ACTIVE AdsManual for a given source name (--set-check-active-by-source)."
    )

    def add_arguments(self, parser):
        # --- tryb 1 (oryginalny) ---
        parser.add_argument(
            "--model",
            help="Limit to a single model, e.g. 'extractly.AdsNetworkMonitoring' (must have inactive_date & is_active).",
        )
        parser.add_argument(
            "--days",
            type=int,
            default=0,
            help="Only deactivate if inactive_date is at least N days in the past (default: 0 = any non-null).",
        )
        parser.add_argument(
            "--print-ids",
            action="store_true",
            help="Print JSON with IDs that would be updated (respects --model and --days).",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Do not perform updates; only show what would change.",
        )

        # --- tryb 2 (NOWE) ---
        parser.add_argument(
            "--set-check-active-by-source",
            dest="set_check_active_source",
            help="Ustaw check_active=True dla wszystkich *aktywnych* AdsManual "
                 "związanych ze źródłami pasującymi po nazwie (np. 'nonline'). "
                 "Dopasowanie działa po SourceHtml.name, SourceNetwork.title i SourceNetwork.name "
                 "(case-insensitive, partial).",
        )

    def handle(self, *args, **opts):
        # === TRYB 2: masowe ustawienie check_active dla aktywnych ogłoszeń danego źródła ===
        source_like = opts.get("set_check_active_source")
        if source_like:
            return self._handle_set_check_active_by_source(
                source_like=source_like,
                dry_run=bool(opts.get("dry_run")),
            )

        # === TRYB 1: oryginalny flow dezaktywacji po inactive_date ===
        model_label = opts.get("model")
        days = int(opts.get("days") or 0)
        print_ids = bool(opts.get("print_ids"))
        dry_run = bool(opts.get("dry_run"))

        if model_label:
            models = [_resolve_model(model_label)]
        else:
            models = list(_eligible_models())
            if not models:
                raise CommandError(
                    "No eligible models found (need 'inactive_date' and 'is_active')."
                )

        cutoff = None
        if days > 0:
            cutoff = now() - timedelta(days=days)

        total_candidates = 0
        total_updated = 0

        grand_total_ads = 0
        grand_inactive_by_flag = 0
        grand_inactive_by_date = 0

        printed_payload = []

        for model in models:
            flag_field = _active_flag_field(model)
            if not flag_field:
                self.stdout.write(self.style.WARNING(
                    f"[SKIP] {model._meta.label}: no is_active field found."
                ))
                continue

            # statystyki
            total_ads = model.objects.count()
            inactive_by_flag = model.objects.filter(**{flag_field: False}).count()
            inactive_by_date = model.objects.filter(inactive_date__isnull=False).count()

            grand_total_ads += total_ads
            grand_inactive_by_flag += inactive_by_flag
            grand_inactive_by_date += inactive_by_date

            self.stdout.write(
                f"[{model._meta.label}] totals: total={total_ads}  "
                f"inactive_by_flag={inactive_by_flag}  inactive_by_date={inactive_by_date}"
            )

            # kandydaci
            qs = model.objects.filter(inactive_date__isnull=False)
            if cutoff is not None:
                qs = qs.filter(inactive_date__lte=cutoff)

            qs = qs.filter(~Q(**{flag_field: False}) | Q(**{f'{flag_field}__isnull': True}))

            count = qs.count()
            total_candidates += count

            self.stdout.write(
                f"[{model._meta.label}] candidates: {count} (flag='{flag_field}', days>={days})"
            )

            if print_ids:
                ids = list(qs.values_list("id", flat=True))
                printed_payload.append({
                    "model": model._meta.label,
                    "flag_field": flag_field,
                    "stats": {
                        "total": total_ads,
                        "inactive_by_flag": inactive_by_flag,
                        "inactive_by_date": inactive_by_date,
                    },
                    "candidates_count": len(ids),
                    "candidate_ids": ids,
                })

            if not dry_run and count:
                updated = qs.update(**{flag_field: False})
                total_updated += updated
                self.stdout.write(self.style.SUCCESS(
                    f"[{model._meta.label}] updated: {updated}"
                ))

        if print_ids:
            print(json.dumps({
                "ts": now().isoformat(),
                "days": days,
                "aggregate": {
                    "models": len(models),
                    "total_ads": grand_total_ads,
                    "inactive_by_flag": grand_inactive_by_flag,
                    "inactive_by_date": grand_inactive_by_date,
                    "total_candidates": total_candidates,
                },
                "per_model": printed_payload,
            }, ensure_ascii=False))

        self.stdout.write(
            f"\nAGGREGATE: models={len(models)}  total_ads={grand_total_ads}  "
            f"inactive_by_flag={grand_inactive_by_flag}  inactive_by_date={grand_inactive_by_date}  "
            f"candidates={total_candidates}"
        )

        if dry_run:
            self.stdout.write(self.style.WARNING("DRY RUN: no changes written."))
        else:
            self.stdout.write(self.style.SUCCESS(f"DONE: updated {total_updated} record(s)."))

    # === Implementacja trybu 2 ===
    def _handle_set_check_active_by_source(self, *, source_like: str, dry_run: bool):
        src_ids = _resolve_source_ids_by_name_like(source_like)
        if not src_ids:
            raise CommandError(
                f"Nie znaleziono SourceNetwork pasujących do '{source_like}' "
                f"(po SourceHtml.name / SourceNetwork.title / SourceNetwork.name)."
            )

        # AdsManual połączone przez NetworkMonitoredPage -> SourceNetwork
        qs = (
            AdsManual.objects
            .filter(
                is_active=True,
                networkmonitoredpage__source_id__in=list(src_ids)
            )
            .exclude(check_active=True)  # aktualizuj tylko te, które jeszcze nie są oflagowane
        )

        count = qs.count()
        self.stdout.write(
            self.style.NOTICE(
                f"[CHECK_ACTIVE FLAG] Źródła={len(src_ids)}  dopasowane aktywne AdsManual={count}  "
                f"(source_like='{source_like}', dry_run={dry_run})"
            )
        )

        if dry_run or count == 0:
            self.stdout.write(self.style.WARNING("DRY RUN lub brak kandydatów – nic nie zmieniono."))
            return

        updated = qs.update(check_active=True)
        self.stdout.write(self.style.SUCCESS(f"Ustawiono check_active=True dla {updated} rekordów."))
