"""Settings API: import filters, system stats."""

import secrets

from quart import Blueprint, jsonify, request
from sqlalchemy import func, select

from ..extensions import get_session
from ..models import AppSetting, Artist, ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag

settings_bp = Blueprint("settings", __name__, url_prefix="/api")


_EDITABLE_FIELDS = (
    "min_width",
    "min_height",
    "skip_transparent",
    "transparency_threshold",
    "skip_single_color",
    "single_color_threshold",
    "single_color_tolerance",
    "phash_threshold",
    "download_rate_limit_seconds",
    "download_validate_files",
    "download_schedule_default_seconds",
    "download_event_retention_days",
    "download_failure_warning_threshold",
    "series_suggest_enabled",
    "series_suggest_threshold",
    "extdl_mega_enabled",
    "extdl_gdrive_enabled",
    "extdl_mediafire_enabled",
    "extdl_dropbox_enabled",
    "extdl_pixeldrain_enabled",
)

# Per-host external-download toggles — all plain booleans, validated uniformly.
_EXTDL_TOGGLE_FIELDS = (
    "extdl_mega_enabled",
    "extdl_gdrive_enabled",
    "extdl_mediafire_enabled",
    "extdl_dropbox_enabled",
    "extdl_pixeldrain_enabled",
)


@settings_bp.route("/settings/import", methods=["GET"])
async def get_import_settings():
    async with get_session() as session:
        row = await ImportSettings.load(session)
    return jsonify({
        "min_width": row.min_width,
        "min_height": row.min_height,
        "skip_transparent": row.skip_transparent,
        "transparency_threshold": row.transparency_threshold,
        "skip_single_color": row.skip_single_color,
        "single_color_threshold": row.single_color_threshold,
        "single_color_tolerance": row.single_color_tolerance,
        "phash_threshold": row.phash_threshold,
        "download_rate_limit_seconds": row.download_rate_limit_seconds,
        "download_validate_files": row.download_validate_files,
        "download_schedule_default_seconds": row.download_schedule_default_seconds,
        "download_event_retention_days": row.download_event_retention_days,
        "download_failure_warning_threshold": row.download_failure_warning_threshold,
        "series_suggest_enabled": row.series_suggest_enabled,
        "series_suggest_threshold": row.series_suggest_threshold,
        "extdl_mega_enabled": row.extdl_mega_enabled,
        "extdl_gdrive_enabled": row.extdl_gdrive_enabled,
        "extdl_mediafire_enabled": row.extdl_mediafire_enabled,
        "extdl_dropbox_enabled": row.extdl_dropbox_enabled,
        "extdl_pixeldrain_enabled": row.extdl_pixeldrain_enabled,
    })


@settings_bp.route("/settings/import", methods=["PATCH"])
async def update_import_settings():
    body = await request.get_json()
    if not isinstance(body, dict):
        return jsonify({"error": "body must be a JSON object"}), 400

    if "phash_threshold" in body and (
        not isinstance(body["phash_threshold"], int)
        or isinstance(body["phash_threshold"], bool)
        or body["phash_threshold"] < 0
    ):
        return jsonify(
            {"error": "phash_threshold must be a non-negative integer"}
        ), 400

    if "download_rate_limit_seconds" in body:
        val = body["download_rate_limit_seconds"]
        if not isinstance(val, (int, float)) or isinstance(val, bool) or val < 0:
            return jsonify(
                {"error": "download_rate_limit_seconds must be a non-negative number"}
            ), 400
    if "download_validate_files" in body and not isinstance(
        body["download_validate_files"], bool
    ):
        return jsonify(
            {"error": "download_validate_files must be a boolean"}
        ), 400

    # FC-3d scheduling knobs — bounds validation.
    def _bad_int(name: str, lo: int, hi: int):
        return jsonify(
            {"error": f"{name} must be an integer in [{lo}, {hi}]"}
        ), 400

    if "download_schedule_default_seconds" in body:
        v = body["download_schedule_default_seconds"]
        if not isinstance(v, int) or isinstance(v, bool) or v < 60 or v > 86400:
            return _bad_int("download_schedule_default_seconds", 60, 86400)
    if "download_event_retention_days" in body:
        v = body["download_event_retention_days"]
        if not isinstance(v, int) or isinstance(v, bool) or v < 1 or v > 3650:
            return _bad_int("download_event_retention_days", 1, 3650)
    if "download_failure_warning_threshold" in body:
        v = body["download_failure_warning_threshold"]
        if not isinstance(v, int) or isinstance(v, bool) or v < 1 or v > 100:
            return _bad_int("download_failure_warning_threshold", 1, 100)

    if "series_suggest_enabled" in body and not isinstance(
        body["series_suggest_enabled"], bool
    ):
        return jsonify(
            {"error": "series_suggest_enabled must be a boolean"}
        ), 400
    for tog in _EXTDL_TOGGLE_FIELDS:
        if tog in body and not isinstance(body[tog], bool):
            return jsonify({"error": f"{tog} must be a boolean"}), 400
    if "series_suggest_threshold" in body:
        v = body["series_suggest_threshold"]
        if not isinstance(v, (int, float)) or isinstance(v, bool) or v < 0 or v > 1:
            return jsonify(
                {"error": "series_suggest_threshold must be a number in [0, 1]"}
            ), 400

    async with get_session() as session:
        row = await ImportSettings.load(session)
        for field in _EDITABLE_FIELDS:
            if field in body:
                setattr(row, field, body[field])
        await session.commit()

    return await get_import_settings()


@settings_bp.route("/system/stats", methods=["GET"])
async def system_stats():
    async with get_session() as session:
        total_images = (await session.execute(select(func.count(ImageRecord.id)))).scalar_one()
        total_tags = (await session.execute(select(func.count(Tag.id)))).scalar_one()
        storage_bytes = (
            (await session.execute(select(func.coalesce(func.sum(ImageRecord.size_bytes), 0)))).scalar_one()
        )
        subscription_count = (await session.execute(
            select(func.count(Artist.id)).where(Artist.is_subscription.is_(True))
        )).scalar_one()

        # Task counts grouped by status
        status_rows = (
            await session.execute(
                select(ImportTask.status, func.count(ImportTask.id)).group_by(ImportTask.status)
            )
        ).all()
        status_counts = {row[0]: row[1] for row in status_rows}

        # Integrity counts — FC-2e.
        integrity_rows = (
            await session.execute(
                select(
                    ImageRecord.integrity_status,
                    func.count(ImageRecord.id),
                ).group_by(ImageRecord.integrity_status)
            )
        ).all()
        integrity_counts = {row[0]: row[1] for row in integrity_rows}

        # Active batch = running batch that still has outstanding work.
        # Plain "most recent running" picks a freshly-created scan that
        # enqueued zero new files and hides the older batch that's
        # actually being processed; the EXISTS clause filters those
        # empty batches out.
        active_batch_row = (
            await session.execute(
                select(ImportBatch)
                .where(
                    ImportBatch.status == "running",
                    select(ImportTask.id)
                    .where(
                        ImportTask.batch_id == ImportBatch.id,
                        ImportTask.status.in_(["pending", "queued", "processing"]),
                    )
                    .exists(),
                )
                .order_by(ImportBatch.started_at.desc())
                .limit(1)
            )
        ).scalar_one_or_none()
        active_batch = None
        if active_batch_row:
            active_batch = {
                "id": active_batch_row.id,
                "source_path": active_batch_row.source_path,
                "started_at": active_batch_row.started_at.isoformat(),
                "total_files": active_batch_row.total_files,
                "imported": active_batch_row.imported,
                "skipped": active_batch_row.skipped,
                "failed": active_batch_row.failed,
            }

    return jsonify({
        "total_images": total_images,
        "total_tags": total_tags,
        "storage_bytes": storage_bytes,
        "subscription_count": int(subscription_count),
        "tasks": {
            "pending": status_counts.get("pending", 0),
            "queued": status_counts.get("queued", 0),
            "processing": status_counts.get("processing", 0),
            "complete": status_counts.get("complete", 0),
            "skipped": status_counts.get("skipped", 0),
            "failed": status_counts.get("failed", 0),
        },
        "integrity": {
            "unknown": int(integrity_counts.get("unknown", 0)),
            "ok": int(integrity_counts.get("ok", 0)),
            "corrupt": int(integrity_counts.get("corrupt", 0)),
            "failed_verification": int(
                integrity_counts.get("failed_verification", 0)
            ),
        },
        "active_batch": active_batch,
    })


# --- FC-3b: extension API key (lives in app_setting) -----------------------


async def _get_or_seed_extension_api_key(session) -> str:
    row = (await session.execute(
        select(AppSetting).where(AppSetting.key == "extension_api_key")
    )).scalar_one_or_none()
    if row is None:
        row = AppSetting(key="extension_api_key", value=secrets.token_urlsafe(32))
        session.add(row)
        await session.commit()
        await session.refresh(row)
    return row.value


@settings_bp.route("/settings/extension_api_key", methods=["GET"])
async def get_extension_api_key():
    async with get_session() as session:
        key = await _get_or_seed_extension_api_key(session)
    return jsonify({"key": key})


@settings_bp.route("/settings/extension_api_key/rotate", methods=["POST"])
async def rotate_extension_api_key():
    async with get_session() as session:
        row = (await session.execute(
            select(AppSetting).where(AppSetting.key == "extension_api_key")
        )).scalar_one_or_none()
        new_value = secrets.token_urlsafe(32)
        if row is None:
            row = AppSetting(key="extension_api_key", value=new_value)
            session.add(row)
        else:
            row.value = new_value
        await session.commit()
    return jsonify({"key": new_value})
