# retoor <retoor@molodetz.nl>

import logging

from devplacepy.database import CONSENT_KINDS, bump_cache_version
from devplacepy.services.acceptance.agreements import (
    AGREEMENTS,
    label_for,
    setting_key,
)
from devplacepy.services.acceptance.grant import converge_user
from devplacepy.services.acceptance.pending import current_version, pending
from devplacepy.services.base import BaseService, ConfigField

logger = logging.getLogger(__name__)

DRY_RUN_KEY = "acceptance_dry_run"
BATCH_SIZE_KEY = "acceptance_batch_size"
DEFAULT_BATCH_SIZE = 200
MAX_BATCH_SIZE = 5000
SAMPLE_NAMES = 20


def sample_names(candidates: list[dict]) -> str:
    names = [row.get("username") or row.get("uid") or "" for row in candidates]
    shown = ", ".join(names[:SAMPLE_NAMES])
    remaining = len(names) - SAMPLE_NAMES
    if remaining > 0:
        return f"{shown} and {remaining} more"
    return shown


class AcceptanceService(BaseService):
    title = "Acceptance convergence"
    description = (
        "Grants every policy agreement to every account that has not declined it, "
        "so a production-identical test instance never interrupts manual testing "
        "with an acceptance dialog. Off by default and never appropriate on a real "
        "production host."
    )
    details = (
        "An account that withdrew a consent is never granted it again, with no "
        "further action: the latest row in the consent ledger is the decline "
        "register. The service only ever grants, so any withdrawal in the ledger "
        "was written by a human. Withdraw a consent from the profile privacy tab "
        "to keep an account permanently outside the convergence."
    )
    default_enabled = False
    min_interval = 60
    METRICS_SECONDS = 60
    config_fields = [
        ConfigField(
            DRY_RUN_KEY,
            "Dry run",
            type="bool",
            default=True,
            help=(
                "Log which accounts would be converged and write nothing. Switch "
                "off only after the log lists what you expect."
            ),
            group="Safety",
        ),
        ConfigField(
            BATCH_SIZE_KEY,
            "Accounts per agreement per run",
            type="int",
            default=DEFAULT_BATCH_SIZE,
            minimum=1,
            maximum=MAX_BATCH_SIZE,
            help="Upper bound on one sweep. The remainder converges on the next run.",
            group="Safety",
        ),
        *[
            ConfigField(
                setting_key(agreement.kind),
                label_for(agreement.kind),
                type="bool",
                default=False,
                help=f"Grant the {agreement.kind} agreement to every account that has not declined it.",
                group="Agreements",
            )
            for agreement in AGREEMENTS
        ],
    ]

    def __init__(self) -> None:
        super().__init__("acceptance", interval_seconds=300)
        self._converged = {agreement.kind: 0 for agreement in AGREEMENTS}

    async def run_once(self) -> None:
        config = self.get_config()
        dry = config[DRY_RUN_KEY]
        limit = config[BATCH_SIZE_KEY]
        gate_changed = False
        for agreement in AGREEMENTS:
            if not config[setting_key(agreement.kind)]:
                continue
            version = current_version(agreement)
            candidates = pending(agreement, limit)
            if not candidates:
                continue
            if dry:
                self.log(
                    f"[dry run] {agreement.kind}: {len(candidates)} account(s) would be "
                    f"converged to version {version}: {sample_names(candidates)}"
                )
                continue
            granted = 0
            for user in candidates:
                if converge_user(agreement, user, version):
                    granted += 1
                    gate_changed = gate_changed or bool(agreement.gate_column)
            self._converged[agreement.kind] += granted
            self.log(
                f"{agreement.kind}: converged {granted} of {len(candidates)} pending "
                f"at version {version}"
            )
            if len(candidates) == limit:
                self.log(
                    f"{agreement.kind}: batch cap of {limit} reached, more remain "
                    "for the next run"
                )
        if gate_changed:
            bump_cache_version("auth")

    def collect_metrics(self) -> dict:
        config = self.get_config()
        limit = config[BATCH_SIZE_KEY]
        enabled = [
            agreement
            for agreement in AGREEMENTS
            if config[setting_key(agreement.kind)]
        ]
        stats = [
            {"label": "Dry run", "value": 1 if config[DRY_RUN_KEY] else 0},
            {"label": "Agreements enabled", "value": len(enabled)},
        ]
        rows = []
        for agreement in AGREEMENTS:
            is_enabled = config[setting_key(agreement.kind)]
            count = len(pending(agreement, limit)) if is_enabled else 0
            display = f"{count}+" if is_enabled and count == limit else str(count)
            if is_enabled:
                stats.append(
                    {"label": f"{agreement.kind} pending", "value": display}
                )
            rows.append(
                [
                    agreement.kind,
                    CONSENT_KINDS.get(agreement.kind, agreement.kind)[:48],
                    1 if is_enabled else 0,
                    agreement.version_setting or "",
                    display if is_enabled else "",
                    self._converged[agreement.kind],
                ]
            )
        table = {
            "columns": [
                "Agreement",
                "Policy",
                "Enabled",
                "Version setting",
                "Pending",
                "Converged",
            ],
            "rows": rows,
        }
        return {"stats": stats, "table": table}
