# Copyright (c) 2023-present Pi Dash Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

# Python imports
import os
import logging

# Third party imports
from datetime import timedelta

from celery import Celery
from pythonjsonlogger.jsonlogger import JsonFormatter
from celery.signals import after_setup_logger, after_setup_task_logger
from celery.schedules import crontab

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pi_dash.settings.production")

app = Celery("pi_dash")

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object("django.conf:settings", namespace="CELERY")

app.conf.beat_schedule = {
    # Intra day recurring jobs
    "check-every-five-minutes-to-send-email-notifications": {
        "task": "pi_dash.bgtasks.email_notification_task.stack_email_notification",
        "schedule": crontab(minute="*/5"),  # Every 5 minutes
    },
    "run-every-6-hours-for-instance-trace": {
        "task": "pi_dash.license.bgtasks.tracer.instance_traces",
        "schedule": crontab(hour="*/6", minute=0),  # Every 6 hours
    },
    # Occurs once every day
    "check-every-day-to-delete-hard-delete": {
        "task": "pi_dash.bgtasks.deletion_task.hard_delete",
        "schedule": crontab(hour=0, minute=0),  # UTC 00:00
    },
    "check-every-day-to-archive-and-close": {
        "task": "pi_dash.bgtasks.issue_automation_task.archive_and_close_old_issues",
        "schedule": crontab(hour=1, minute=0),  # UTC 01:00
    },
    "check-every-day-to-delete_exporter_history": {
        "task": "pi_dash.bgtasks.exporter_expired_task.delete_old_s3_link",
        "schedule": crontab(hour=1, minute=30),  # UTC 01:30
    },
    "check-every-day-to-delete-file-asset": {
        "task": "pi_dash.bgtasks.file_asset_task.delete_unuploaded_file_asset",
        "schedule": crontab(hour=2, minute=0),  # UTC 02:00
    },
    "check-every-day-to-delete-api-logs": {
        "task": "pi_dash.bgtasks.cleanup_task.delete_api_logs",
        "schedule": crontab(hour=2, minute=30),  # UTC 02:30
    },
    "check-every-day-to-delete-email-notification-logs": {
        "task": "pi_dash.bgtasks.cleanup_task.delete_email_notification_logs",
        "schedule": crontab(hour=2, minute=45),  # UTC 02:45
    },
    "check-every-day-to-delete-page-versions": {
        "task": "pi_dash.bgtasks.cleanup_task.delete_page_versions",
        "schedule": crontab(hour=3, minute=0),  # UTC 03:00
    },
    "check-every-day-to-delete-issue-description-versions": {
        "task": "pi_dash.bgtasks.cleanup_task.delete_issue_description_versions",
        "schedule": crontab(hour=3, minute=15),  # UTC 03:15
    },
    "check-every-day-to-delete-webhook-logs": {
        "task": "pi_dash.bgtasks.cleanup_task.delete_webhook_logs",
        "schedule": crontab(hour=3, minute=30),  # UTC 03:30
    },
    "check-every-day-to-delete-exporter-history": {
        "task": "pi_dash.bgtasks.exporter_expired_task.delete_old_s3_link",
        "schedule": crontab(hour=3, minute=45),  # UTC 03:45
    },
    # Runner lifecycle
    "runner-expire-stale-approvals": {
        "task": "runner.expire_stale_approvals",
        "schedule": crontab(minute="*/1"),
    },
    "runner-mark-offline-runners": {
        "task": "runner.mark_offline_runners",
        "schedule": crontab(minute="*/1"),
    },
    # Per-active-run agent stall watchdog. Backstop for the runner's own
    # internal 5-minute stall timer; see
    # `.ai_design/runner_agent_bridge/design.md` §4.5.3. 30s gives ~12
    # ticks per stall threshold so we don't oversleep the deadline.
    "runner-reconcile-stalled-runs": {
        "task": "runner.reconcile_stalled_runs",
        "schedule": timedelta(seconds=30),
    },
    "runner-sweep-agent-chat-state": {
        "task": "runner.sweep_agent_chat_state",
        "schedule": timedelta(seconds=30),
    },
    "runner-sweep-chat-message-dedupe": {
        "task": "runner.sweep_chat_message_dedupe",
        "schedule": crontab(hour=4, minute=0),
    },
    # Issue ticking — see .ai_design/issue_ticking_system/design.md §6
    "scan-due-agent-tickers": {
        "task": "pi_dash.bgtasks.agent_ticker.scan_due_tickers",
        "schedule": crontab(minute="*"),
    },
    # GitHub Issue Sync — see .ai_design/github_sync/design.md §6.3.
    "github-issue-sync-every-4h": {
        "task": "pi_dash.bgtasks.github_sync_task.sync_all_repos",
        "schedule": crontab(minute=0, hour="*/4"),
    },
    # Project Scheduler scanner — see .ai_design/project_scheduler/design.md §6.1.
    "scan-due-scheduler-bindings": {
        "task": "pi_dash.bgtasks.scheduler.scan_due_bindings",
        "schedule": crontab(minute="*"),
    },
}


# Setup logging
@after_setup_logger.connect
def setup_loggers(logger, *args, **kwargs):
    formatter = JsonFormatter('"%(levelname)s %(asctime)s %(module)s %(name)s %(message)s')
    handler = logging.StreamHandler()
    handler.setFormatter(fmt=formatter)
    logger.addHandler(handler)


@after_setup_task_logger.connect
def setup_task_loggers(logger, *args, **kwargs):
    formatter = JsonFormatter('"%(levelname)s %(asctime)s %(module)s %(name)s %(message)s')
    handler = logging.StreamHandler()
    handler.setFormatter(fmt=formatter)
    logger.addHandler(handler)


# Load task modules from all registered Django app configs.
app.autodiscover_tasks()

app.conf.beat_scheduler = "django_celery_beat.schedulers.DatabaseScheduler"
