# retoor <retoor@molodetz.nl>

from __future__ import annotations

import logging
from datetime import datetime, timedelta, timezone

from sqlalchemy import text

from devplacepy.database import (
    _now_iso,
    db,
    get_int_setting,
    get_table,
    purge_event,
    soft_delete,
    soft_delete_in,
)
from devplacepy.utils import clear_user_cache

logger = logging.getLogger(__name__)


DEFAULT_GRACE_HOURS = 24

TOMBSTONE_UID_CHARS = 12


OWNED_TABLES: tuple[tuple[str, str], ...] = (
    ("posts", "user_uid"),
    ("comments", "user_uid"),
    ("gists", "user_uid"),
    ("projects", "user_uid"),
    ("quizzes", "user_uid"),
    ("quiz_attempts", "user_uid"),
    ("quiz_answers", "user_uid"),
    ("attachments", "user_uid"),
    ("votes", "user_uid"),
    ("reactions", "user_uid"),
    ("bookmarks", "user_uid"),
    ("poll_votes", "user_uid"),
    ("follows", "follower_uid"),
    ("follows", "following_uid"),
    ("awards", "receiver_uid"),
    ("awards", "giver_uid"),
    ("user_relations", "user_uid"),
    ("user_relations", "target_uid"),
    ("notification_preferences", "user_uid"),
    ("issue_tickets", "user_uid"),
    ("issue_comment_authors", "user_uid"),
    ("sessions", "user_uid"),
    ("access_tokens", "user_uid"),
    ("devrant_tokens", "user_uid"),
    ("email_accounts", "user_uid"),
    ("devii_conversations", "owner_id"),
    ("devii_tasks", "owner_id"),
    ("devii_lessons", "owner_id"),
    ("devii_virtual_tools", "owner_id"),
    ("user_customizations", "owner_id"),
    ("deepsearch_sessions", "owner_id"),
    ("isslop_analyses", "owner_id"),
)

CHILD_TABLES: tuple[tuple[str, str, str, str], ...] = (
    ("project_files", "project_uid", "projects", "user_uid"),
    ("project_forks", "project_uid", "projects", "user_uid"),
    ("polls", "post_uid", "posts", "user_uid"),
    ("quiz_questions", "quiz_uid", "quizzes", "user_uid"),
    ("deepsearch_messages", "session_uid", "deepsearch_sessions", "owner_id"),
)

ANONYMISED_FIELDS: tuple[str, ...] = (
    "email",
    "bio",
    "location",
    "git_link",
    "website",
    "avatar_seed",
    "api_key",
    "password_hash",
    "timezone",
    "last_seen",
    "suspension_reason",
    "suspended_until",
)


def grace_hours() -> int:
    return max(0, get_int_setting("account_deletion_grace_hours", DEFAULT_GRACE_HOURS))


def tombstone_username(uid: str) -> str:
    return f"deleted_{uid.replace('-', '')[:TOMBSTONE_UID_CHARS]}"


def _child_uids(parent_table: str, owner_column: str, user_uid: str) -> list[str]:
    if parent_table not in db.tables:
        return []
    table = get_table(parent_table)
    if not table.has_column(owner_column) or not table.has_column("uid"):
        return []
    return [row["uid"] for row in table.find(**{owner_column: user_uid}) if row.get("uid")]


def cascade(user_uid: str, stamp: str) -> int:
    removed = 0
    for table_name, child_column, parent_table, owner_column in CHILD_TABLES:
        if table_name not in db.tables:
            continue
        if not get_table(table_name).has_column(child_column):
            continue
        parents = _child_uids(parent_table, owner_column, user_uid)
        if parents:
            removed += soft_delete_in(
                table_name, child_column, parents, user_uid, stamp=stamp
            )
    for table_name, column in OWNED_TABLES:
        if table_name not in db.tables:
            continue
        if not get_table(table_name).has_column(column):
            continue
        removed += soft_delete(table_name, user_uid, stamp=stamp, **{column: user_uid})
    return removed


def anonymise(user: dict, stamp: str) -> None:
    changes = {
        "uid": user["uid"],
        "username": tombstone_username(user["uid"]),
        "is_active": False,
        "deletion_requested_at": stamp,
        "role": "Member",
    }
    for column in ANONYMISED_FIELDS:
        changes[column] = ""
    get_table("users").update(changes, ["uid"])
    clear_user_cache(user["uid"])


def claim_deletion(user_uid: str, stamp: str) -> bool:
    with db:
        result = db.executable.execute(
            text(
                "UPDATE users SET deletion_requested_at = :stamp "
                "WHERE uid = :uid AND COALESCE(deletion_requested_at, '') = ''"
            ),
            {"stamp": stamp, "uid": user_uid},
        )
    return result.rowcount == 1


def delete_account(user: dict) -> dict | None:
    from devplacepy.database import invalidate_admins_cache, invalidate_user_relations
    from devplacepy.services.moderation.enforcement import revoke_sessions

    stamp = _now_iso()
    if not claim_deletion(user["uid"], stamp):
        logger.info(f"Account {user['uid']} was already being deleted")
        return None
    revoke_sessions(user["uid"])
    removed = cascade(user["uid"], stamp)
    anonymise(user, stamp)
    invalidate_admins_cache()
    invalidate_user_relations(user["uid"])
    logger.info(f"Account {user['uid']} deleted, {removed} rows removed under {stamp}")
    return {"stamp": stamp, "rows": removed, "grace_hours": grace_hours()}


def due_purges(now: datetime | None = None) -> list[dict]:
    if "users" not in db.tables:
        return []
    table = get_table("users")
    if not table.has_column("deletion_requested_at"):
        return []
    moment = now or datetime.now(timezone.utc)
    cutoff = (moment - timedelta(hours=grace_hours())).isoformat()
    rows = db.query(
        "SELECT uid, deletion_requested_at FROM users "
        "WHERE deletion_requested_at IS NOT NULL AND deletion_requested_at != '' "
        "AND deletion_requested_at <= :cutoff",
        cutoff=cutoff,
    )
    return [dict(row) for row in rows]


def purge_due(now: datetime | None = None) -> int:
    from devplacepy.services.audit import record as audit

    purged = 0
    for row in due_purges(now):
        tables = purge_event(row["deletion_requested_at"])
        _purge_user_row(row["uid"])
        purged += 1
        audit.record_system(
            "account.delete.purge",
            target_type="user",
            target_uid=row["uid"],
            summary=f"purged deleted account {row['uid']} after the grace window",
            metadata={
                "stamp": row["deletion_requested_at"],
                "tables": [name for name, _ in tables],
            },
            links=[audit.target("user", row["uid"])],
        )
    return purged


def _purge_user_row(user_uid: str) -> None:
    with db:
        db.query("DELETE FROM users WHERE uid = :uid", uid=user_uid)
    clear_user_cache(user_uid)
