"""
Stop retry/alert bleeding for one Facebook ad account.

Usage from repo root:
  uv run python manage.py shell < /tmp/stop_fb_account_336282307415329_spam.py

Dry-run is the default. Apply changes with:
  APPLY=1 uv run python manage.py shell < /tmp/stop_fb_account_336282307415329_spam.py
"""

import os

from celery import current_app
from django.db import transaction
from django.utils import timezone

from creative_module.models import AdCreativeData, AdIdsCacheData, AdMetricCacheData, AccountSyncLog
from creative_module.types import UpdateStatus
from data_pipeline_module.constants import SyncStatus
from data_pipeline_module.models import BreakdownSyncStatus, PipelineSyncStatus
from jobs.models import Job, JobTask
from organization_auth.constants import UserRoleType
from organization_auth.models import Account, UserAccount


AD_ACCOUNT_ID = "336282307415329"
PLATFORM = Account.Platform.FACEBOOK
EXPECTED_ACCOUNT_ID = "4e7e1ec9-8754-459e-b8b7-7620db56f52c"
SUPPRESSION_REASON = (
    "Suppressed manually: Facebook returned (#200) missing ads_management/ads_read "
    "for ad account 336282307415329."
)

APPLY = 1
REVOKE_TASKS = 1


def print_section(title):
    print("\n" + "=" * 80)
    print(title)
    print("=" * 80)


def count(qs):
    return qs.count()


def task_ids_from_qs(qs, field_name):
    return list(
        qs.exclude(**{f"{field_name}__isnull": True})
        .exclude(**{field_name: ""})
        .values_list(field_name, flat=True)
        .distinct()
    )


account = Account.objects.get(ad_account_id=AD_ACCOUNT_ID, platform=PLATFORM)
if str(account.id) != EXPECTED_ACCOUNT_ID:
    raise RuntimeError(f"Refusing to run: resolved account {account.id}, expected {EXPECTED_ACCOUNT_ID}")

print_section("Target")
print(f"apply={APPLY}")
print(f"account_id={account.id}")
print(f"ad_account_id={account.ad_account_id}")
print(f"name={account.ad_account_name}")
print(f"platform={account.platform}")
print(f"version={account.version}")
print(f"assets_update_status={account.assets_update_status}")
print(f"account.task_id={account.task_id}")

active_owner_qs = UserAccount.objects.filter(
    account=account,
    role=UserRoleType.OWNER.value,
    status=UserAccount.UserAccountStatus.ACTIVE,
)

pipeline_qs = PipelineSyncStatus.objects.filter(account=account, platform=PLATFORM)
pipeline_open_qs = pipeline_qs.filter(
    status__in=[SyncStatus.PENDING.value, SyncStatus.RUNNING.value, SyncStatus.FAILED.value]
)

breakdown_qs = BreakdownSyncStatus.objects.filter(account=account, platform=PLATFORM)
breakdown_open_qs = breakdown_qs.filter(
    status__in=[SyncStatus.PENDING.value, SyncStatus.RUNNING.value, SyncStatus.FAILED.value]
)

ad_ids_open_qs = AdIdsCacheData.objects.filter(
    account=account,
    sync_status__in=[UpdateStatus.PENDING.value, UpdateStatus.FAILED.value],
)
metric_open_qs = AdMetricCacheData.objects.filter(
    account=account,
    sync_status__in=[UpdateStatus.PENDING.value, UpdateStatus.FAILED.value],
)
creative_open_qs = AdCreativeData.objects.filter(
    account=account,
    sync_status__in=[UpdateStatus.PENDING.value, UpdateStatus.FAILED.value],
)

run_open_qs = AccountSyncLog.objects.filter(
    account=account,
    overall_status__in=[
        AccountSyncLog.OverallStatus.QUEUED,
        AccountSyncLog.OverallStatus.RUNNING,
        AccountSyncLog.OverallStatus.RECONCILING,
    ],
)

job_open_qs = Job.objects.filter(
    account=account,
    state__in=[Job.JobState.QUEUED, Job.JobState.RUNNING],
)
job_task_open_qs = JobTask.objects.filter(
    job__account=account,
    state__in=[JobTask.TaskState.PENDING, JobTask.TaskState.RECEIVED, JobTask.TaskState.STARTED],
)

task_ids = set()
if account.task_id:
    task_ids.add(account.task_id)
task_ids.update(task_ids_from_qs(pipeline_open_qs, "celery_task_id"))
task_ids.update(task_ids_from_qs(breakdown_open_qs, "celery_task_id"))
task_ids.update(task_ids_from_qs(ad_ids_open_qs, "task_id"))
task_ids.update(task_ids_from_qs(metric_open_qs, "task_id"))
task_ids.update(task_ids_from_qs(creative_open_qs, "task_id"))
task_ids.update(task_ids_from_qs(job_open_qs, "group_id"))
task_ids.update(task_ids_from_qs(job_open_qs, "chord_id"))
task_ids.update(task_ids_from_qs(job_task_open_qs, "task_id"))
task_ids = sorted(task_ids)

print_section("Planned Changes")
print(f"active owner UserAccount rows to mark EXPIRED: {count(active_owner_qs)}")
print(f"pipeline rows PENDING/RUNNING/FAILED to mark SKIPPED: {count(pipeline_open_qs)}")
print(f"breakdown rows PENDING/RUNNING/FAILED to mark SKIPPED: {count(breakdown_open_qs)}")
print(f"AdIdsCacheData pending/failed rows to mark failed and clear task_id: {count(ad_ids_open_qs)}")
print(f"AdMetricCacheData pending/failed rows to mark failed and clear task_id: {count(metric_open_qs)}")
print(f"AdCreativeData pending/failed rows to mark failed and cap retries: {count(creative_open_qs)}")
print(f"AccountSyncLog open rows to mark failed: {count(run_open_qs)}")
print(f"Job queued/running rows to mark failed: {count(job_open_qs)}")
print(f"JobTask open rows to mark failed: {count(job_task_open_qs)}")
print(f"task ids to revoke: {len(task_ids)}")
for task_id in task_ids[:50]:
    print(f"  {task_id}")
if len(task_ids) > 50:
    print(f"  ... {len(task_ids) - 50} more")

if not APPLY:
    print_section("Dry Run")
    print("No changes made. Re-run with APPLY=1 to update rows and revoke tasks.")
    raise SystemExit(0)

now = timezone.now()

with transaction.atomic():
    active_owner_qs.update(
        status=UserAccount.UserAccountStatus.EXPIRED,
        last_token_check_time=now,
    )

    Account.objects.filter(pk=account.pk).update(
        assets_update_status=UpdateStatus.FAILED.value,
        task_id=None,
    )

    pipeline_open_qs.update(
        status=SyncStatus.SKIPPED.value,
        completed_at=now,
        last_error=SUPPRESSION_REASON,
        celery_task_id=None,
    )
    breakdown_open_qs.update(
        status=SyncStatus.SKIPPED.value,
        completed_at=now,
        last_error=SUPPRESSION_REASON,
        celery_task_id=None,
    )

    ad_ids_open_qs.update(
        sync_status=UpdateStatus.FAILED.value,
        error_message=SUPPRESSION_REASON,
        task_id=None,
    )
    metric_open_qs.update(
        sync_status=UpdateStatus.FAILED.value,
        error_message=SUPPRESSION_REASON,
        task_id=None,
    )
    creative_open_qs.update(
        sync_status=UpdateStatus.FAILED.value,
        error_message=SUPPRESSION_REASON,
        task_id=None,
        creative_sync_retry_count=3,
    )

    run_open_qs.update(
        overall_status=AccountSyncLog.OverallStatus.FAILED,
        current_stage=AccountSyncLog.Stage.CLICKHOUSE,
        ad_creative_sync_status=AccountSyncLog.StageStatus.FAILED,
        stats_sync_status=AccountSyncLog.StageStatus.FAILED,
        ai_reports_status=AccountSyncLog.StageStatus.FAILED,
        finished_at=now,
    )

    job_open_qs.update(
        state=Job.JobState.FAILED,
        finished_at=now,
    )
    job_task_open_qs.update(
        state=JobTask.TaskState.FAILED,
        finished_at=now,
    )

print_section("Task Revoke")
if REVOKE_TASKS:
    revoked = 0
    failed = 0
    for task_id in task_ids:
        try:
            current_app.control.revoke(task_id, terminate=True, signal="SIGTERM")
            revoked += 1
        except Exception as exc:
            failed += 1
            print(f"failed to revoke {task_id}: {exc}")
    print(f"revoked={revoked} failed={failed}")
else:
    print("Skipped because REVOKE_TASKS=0")

print_section("Done")
print("Suppression applied for Facebook ad_account_id=336282307415329 only.")
