#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""OK ITHAKI V249382 unified account and professional-lock service.

Security design:
- one shared public account name/avatar for normal and professional mode,
- no email or telephone fields,
- PBKDF2-HMAC-SHA256 password hashes with random salt,
- professional password requested for each menu entry,
- five wrong attempts -> 30 minute lock,
- another five wrong attempts after that lock -> 24 hour lock,
- short server-side professional sessions stored only as token hashes,
- replay/rate/honeypot checks for sensitive operations,
- audit records store hashed device identifiers, never raw passwords.
"""
from __future__ import annotations

from pathlib import Path
from typing import Any
import base64
import hashlib
import hmac
import json
import secrets
import sqlite3
import time
import uuid

VERSION = "V249386-professional-code-confirmation-v249382-data"
BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = BASE_DIR / "data"
DB_PATH = DATA_DIR / "account_security_v249382.sqlite3"

PBKDF2_ITERATIONS = 260_000
SESSION_SECONDS = 20 * 60
FIRST_LOCK_SECONDS = 30 * 60
SECOND_LOCK_SECONDS = 24 * 60 * 60
MAX_AVATAR_CHARS = 1_800_000
MAX_NAME_CHARS = 80


class AccountError(RuntimeError):
    pass


def _now() -> float:
    return time.time()


def _clean(value: Any, limit: int = 500) -> str:
    return str(value or "").replace("\x00", " ").strip()[:limit]


def _device_hash(room_id: str, device_id: str) -> str:
    raw = f"{_clean(room_id,120)}|{_clean(device_id,160)}".encode("utf-8", "ignore")
    return hashlib.sha256(raw).hexdigest()


def _token_hash(token: str) -> str:
    return hashlib.sha256(_clean(token, 500).encode("utf-8", "ignore")).hexdigest()


def _password_digest(password: str, salt: bytes, iterations: int = PBKDF2_ITERATIONS) -> str:
    return hashlib.pbkdf2_hmac(
        "sha256",
        password.encode("utf-8"),
        salt,
        int(iterations),
        dklen=32,
    ).hex()


def _password_pair_valid(password: str, confirmation: str) -> tuple[bool, str]:
    if password != confirmation:
        return False, "Οι δύο κωδικοί δεν είναι ίδιοι. Η ενεργοποίηση δεν πραγματοποιήθηκε."
    return _password_valid(password)


def _password_valid(password: str) -> tuple[bool, str]:
    if len(password) < 8:
        return False, "Ο επαγγελματικός κωδικός χρειάζεται τουλάχιστον 8 χαρακτήρες."
    if len(password) > 128:
        return False, "Ο κωδικός είναι μεγαλύτερος από το επιτρεπτό όριο."
    if len(set(password)) < 4:
        return False, "Χρησιμοποίησε λιγότερο προβλέψιμο κωδικό."
    if not any(ch.isalpha() for ch in password):
        return False, "Ο κωδικός χρειάζεται τουλάχιστον ένα γράμμα."
    if not any(ch.isdigit() for ch in password):
        return False, "Ο κωδικός χρειάζεται τουλάχιστον έναν αριθμό."
    return True, ""


def _validate_name(value: Any) -> str:
    name = _clean(value, MAX_NAME_CHARS)
    if len(name) < 2 or name.casefold() in {"μέλος", "member", "ανώνυμος", "anonymous"}:
        raise AccountError("Χρειάζεται κανονικό όνομα μέλους.")
    return name


def _validate_avatar(value: Any) -> str:
    avatar = _clean(value, MAX_AVATAR_CHARS + 1)
    if len(avatar) > MAX_AVATAR_CHARS:
        raise AccountError("Η φωτογραφία προφίλ είναι πολύ μεγάλη.")
    if avatar and not (
        avatar.startswith("data:image/")
        or avatar.startswith("animal:")
        or avatar.startswith("/")
        or avatar.startswith("https://")
        or avatar.startswith("http://")
        or len(avatar) <= 16
    ):
        raise AccountError("Μη έγκυρη φωτογραφία προφίλ.")
    return avatar


def _conn() -> sqlite3.Connection:
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    con = sqlite3.connect(str(DB_PATH), timeout=30)
    con.row_factory = sqlite3.Row
    con.execute("PRAGMA journal_mode=WAL")
    con.execute("PRAGMA synchronous=NORMAL")
    con.execute("PRAGMA busy_timeout=30000")
    return con


def init_db() -> None:
    with _conn() as con:
        con.executescript(
            """
            CREATE TABLE IF NOT EXISTS accounts_v249382(
              room_id TEXT NOT NULL,
              device_id TEXT NOT NULL,
              account_name TEXT NOT NULL DEFAULT '',
              avatar TEXT NOT NULL DEFAULT '',
              account_mode TEXT NOT NULL DEFAULT 'normal',
              professional_enabled INTEGER NOT NULL DEFAULT 0,
              password_salt TEXT NOT NULL DEFAULT '',
              password_hash TEXT NOT NULL DEFAULT '',
              password_iterations INTEGER NOT NULL DEFAULT 260000,
              failed_attempts INTEGER NOT NULL DEFAULT 0,
              lock_stage INTEGER NOT NULL DEFAULT 0,
              lock_until REAL NOT NULL DEFAULT 0,
              last_failed_at REAL NOT NULL DEFAULT 0,
              last_success_at REAL NOT NULL DEFAULT 0,
              created_at REAL NOT NULL,
              updated_at REAL NOT NULL,
              PRIMARY KEY(room_id,device_id)
            );

            CREATE TABLE IF NOT EXISTS professional_sessions_v249382(
              session_hash TEXT PRIMARY KEY,
              room_id TEXT NOT NULL,
              device_id TEXT NOT NULL,
              created_at REAL NOT NULL,
              expires_at REAL NOT NULL,
              last_used_at REAL NOT NULL,
              revoked_at REAL NOT NULL DEFAULT 0
            );
            CREATE INDEX IF NOT EXISTS idx_prof_sessions382_device
              ON professional_sessions_v249382(room_id,device_id,expires_at);

            CREATE TABLE IF NOT EXISTS security_requests_v249382(
              request_id TEXT PRIMARY KEY,
              actor_hash TEXT NOT NULL,
              action TEXT NOT NULL,
              created_at REAL NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_security_requests382_actor
              ON security_requests_v249382(actor_hash,action,created_at);

            CREATE TABLE IF NOT EXISTS security_audit_v249382(
              event_id TEXT PRIMARY KEY,
              actor_hash TEXT NOT NULL,
              action TEXT NOT NULL,
              success INTEGER NOT NULL,
              detail TEXT NOT NULL DEFAULT '',
              created_at REAL NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_security_audit382_actor
              ON security_audit_v249382(actor_hash,created_at DESC);
            """
        )
        con.commit()


def _audit(con: sqlite3.Connection, actor_hash: str, action: str, success: bool, detail: str = "") -> None:
    con.execute(
        """
        INSERT INTO security_audit_v249382(event_id,actor_hash,action,success,detail,created_at)
        VALUES(?,?,?,?,?,?)
        """,
        (
            "secaud_" + uuid.uuid4().hex,
            actor_hash,
            _clean(action, 80),
            1 if success else 0,
            _clean(detail, 300),
            _now(),
        ),
    )


def _guard(
    con: sqlite3.Connection,
    payload: dict[str, Any],
    action: str,
    *,
    limit: int,
    window_seconds: int,
) -> tuple[str, str, str]:
    room_id = _clean(payload.get("room_id"), 120) or "ithaki-phone-test"
    device_id = _clean(payload.get("device_id"), 160)
    if not device_id:
        raise AccountError("Λείπει η ταυτότητα της συσκευής.")

    # Honeypot fields are intentionally never displayed by the client.
    for key in ("website", "company_url", "_bot_field"):
        if _clean(payload.get(key), 100):
            raise AccountError("Το αίτημα απορρίφθηκε από την προστασία bot.")

    ts_raw = payload.get("client_ts")
    try:
        client_ts = float(ts_raw or 0)
        if client_ts > 10_000_000_000:
            client_ts /= 1000.0
    except Exception:
        client_ts = 0
    if not client_ts or abs(_now() - client_ts) > 10 * 60:
        raise AccountError("Το αίτημα έληξε. Άνοιξε ξανά την οθόνη.")

    request_id = _clean(payload.get("request_id"), 100)
    if len(request_id) < 12:
        raise AccountError("Λείπει ο ασφαλής κωδικός αιτήματος.")

    actor_hash = _device_hash(room_id, device_id)
    cutoff = _now() - max(1, int(window_seconds))
    con.execute("DELETE FROM security_requests_v249382 WHERE created_at<?", (_now() - 2 * 24 * 60 * 60,))

    duplicate = con.execute(
        "SELECT 1 FROM security_requests_v249382 WHERE request_id=?",
        (request_id,),
    ).fetchone()
    if duplicate:
        raise AccountError("Το ίδιο αίτημα έχει ήδη χρησιμοποιηθεί.")

    count = con.execute(
        """
        SELECT COUNT(*) c FROM security_requests_v249382
        WHERE actor_hash=? AND action=? AND created_at>=?
        """,
        (actor_hash, action, cutoff),
    ).fetchone()["c"]
    if int(count or 0) >= int(limit):
        raise AccountError("Πολλά αιτήματα σε μικρό χρόνο. Δοκίμασε αργότερα.")

    con.execute(
        """
        INSERT INTO security_requests_v249382(request_id,actor_hash,action,created_at)
        VALUES(?,?,?,?)
        """,
        (request_id, actor_hash, action, _now()),
    )
    return room_id, device_id, actor_hash


def _account(con: sqlite3.Connection, room_id: str, device_id: str) -> sqlite3.Row | None:
    return con.execute(
        "SELECT * FROM accounts_v249382 WHERE room_id=? AND device_id=?",
        (room_id, device_id),
    ).fetchone()


def _remaining(lock_until: float) -> int:
    return max(0, int(float(lock_until or 0) - _now()))


def _public_account(row: sqlite3.Row | None, room_id: str, device_id: str) -> dict[str, Any]:
    if row is None:
        return {
            "ok": True,
            "version": VERSION,
            "room_id": room_id,
            "device_id": device_id,
            "account_name": "",
            "avatar": "",
            "account_mode": "normal",
            "professional_enabled": False,
            "member_ready": False,
            "locked": False,
            "lock_seconds": 0,
            "failed_attempts": 0,
            "lock_stage": 0,
            "privacy": {
                "email_stored": False,
                "phone_stored": False,
                "password_plaintext_stored": False,
            },
        }
    remaining = _remaining(row["lock_until"])
    name = _clean(row["account_name"], MAX_NAME_CHARS)
    return {
        "ok": True,
        "version": VERSION,
        "room_id": room_id,
        "device_id": device_id,
        "account_name": name,
        "avatar": _clean(row["avatar"], MAX_AVATAR_CHARS),
        "account_mode": row["account_mode"] if row["account_mode"] in ("normal", "professional") else "normal",
        "professional_enabled": bool(row["professional_enabled"]),
        "member_ready": bool(name and name.casefold() not in {"μέλος", "member"}),
        "locked": remaining > 0,
        "lock_seconds": remaining,
        "failed_attempts": int(row["failed_attempts"] or 0),
        "lock_stage": int(row["lock_stage"] or 0),
        "last_success_at": float(row["last_success_at"] or 0),
        "privacy": {
            "email_stored": False,
            "phone_stored": False,
            "password_plaintext_stored": False,
        },
    }


def status(room_id: str, device_id: str) -> dict[str, Any]:
    init_db()
    room_id = _clean(room_id, 120) or "ithaki-phone-test"
    device_id = _clean(device_id, 160)
    if not device_id:
        return _public_account(None, room_id, "")
    with _conn() as con:
        row = _account(con, room_id, device_id)
        return _public_account(row, room_id, device_id)


def sync_profile(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    name = _validate_name(payload.get("account_name") or payload.get("nickname"))
    avatar = _validate_avatar(payload.get("avatar"))
    mode = _clean(payload.get("account_mode"), 30)
    if mode not in ("normal", "professional"):
        mode = "normal"

    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "profile_sync", limit=20, window_seconds=60 * 60
        )
        ts = _now()
        old = _account(con, room_id, device_id)
        if old is None:
            con.execute(
                """
                INSERT INTO accounts_v249382(
                  room_id,device_id,account_name,avatar,account_mode,
                  professional_enabled,password_salt,password_hash,password_iterations,
                  failed_attempts,lock_stage,lock_until,last_failed_at,last_success_at,
                  created_at,updated_at
                ) VALUES(?,?,?,?,?,0,'','',?,0,0,0,0,0,?,?)
                """,
                (room_id, device_id, name, avatar, "normal", PBKDF2_ITERATIONS, ts, ts),
            )
        else:
            # Normal profile sync never silently enters professional mode.
            safe_mode = "normal" if mode == "professional" else mode
            con.execute(
                """
                UPDATE accounts_v249382
                SET account_name=?,avatar=?,account_mode=?,updated_at=?
                WHERE room_id=? AND device_id=?
                """,
                (name, avatar, safe_mode, ts, room_id, device_id),
            )
        _audit(con, actor_hash, "profile_sync", True, "shared-name-avatar")
        con.commit()
        return _public_account(_account(con, room_id, device_id), room_id, device_id)


def setup_professional(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    password = str(payload.get("password") or "")
    password_confirm = str(payload.get("password_confirm") or "")
    valid, note = _password_pair_valid(password, password_confirm)
    if not valid:
        return {"ok": False, "version": VERSION, "note": note}
    name = _validate_name(payload.get("account_name") or payload.get("nickname"))
    avatar = _validate_avatar(payload.get("avatar"))

    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "professional_setup", limit=5, window_seconds=60 * 60
        )
        row = _account(con, room_id, device_id)
        if row is not None and bool(row["professional_enabled"]):
            _audit(con, actor_hash, "professional_setup", False, "already-enabled")
            con.commit()
            return {
                "ok": False,
                "version": VERSION,
                "note": "Ο επαγγελματικός λογαριασμός είναι ήδη ενεργός.",
            }

        salt = secrets.token_bytes(24)
        digest = _password_digest(password, salt)
        ts = _now()
        salt_text = base64.urlsafe_b64encode(salt).decode("ascii")
        if row is None:
            con.execute(
                """
                INSERT INTO accounts_v249382(
                  room_id,device_id,account_name,avatar,account_mode,
                  professional_enabled,password_salt,password_hash,password_iterations,
                  failed_attempts,lock_stage,lock_until,last_failed_at,last_success_at,
                  created_at,updated_at
                ) VALUES(?,?,?,?,?,1,?,?,?,0,0,0,0,0,?,?)
                """,
                (
                    room_id, device_id, name, avatar, "normal",
                    salt_text, digest, PBKDF2_ITERATIONS, ts, ts,
                ),
            )
        else:
            con.execute(
                """
                UPDATE accounts_v249382
                SET account_name=?,avatar=?,account_mode='normal',
                    professional_enabled=1,password_salt=?,password_hash=?,
                    password_iterations=?,failed_attempts=0,lock_stage=0,
                    lock_until=0,last_failed_at=0,updated_at=?
                WHERE room_id=? AND device_id=?
                """,
                (
                    name, avatar, salt_text, digest, PBKDF2_ITERATIONS,
                    ts, room_id, device_id,
                ),
            )
        _audit(con, actor_hash, "professional_setup", True, "enabled")
        con.commit()
        return {
            **_public_account(_account(con, room_id, device_id), room_id, device_id),
            "note": "Ο επαγγελματικός λογαριασμός ενεργοποιήθηκε με κοινό όνομα και φωτογραφία.",
        }


def _verify_password(row: sqlite3.Row, password: str) -> bool:
    try:
        salt = base64.urlsafe_b64decode(str(row["password_salt"]).encode("ascii"))
        expected = str(row["password_hash"] or "")
        actual = _password_digest(password, salt, int(row["password_iterations"] or PBKDF2_ITERATIONS))
        return bool(expected) and hmac.compare_digest(expected, actual)
    except Exception:
        return False


def unlock_professional(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    password = str(payload.get("password") or "")

    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "professional_unlock", limit=14, window_seconds=10 * 60
        )
        row = _account(con, room_id, device_id)
        if row is None or not bool(row["professional_enabled"]):
            _audit(con, actor_hash, "professional_unlock", False, "not-enabled")
            con.commit()
            return {"ok": False, "version": VERSION, "note": "Δεν έχει ενεργοποιηθεί επαγγελματικός λογαριασμός."}

        remaining = _remaining(row["lock_until"])
        if remaining > 0:
            _audit(con, actor_hash, "professional_unlock", False, "locked")
            con.commit()
            return {
                "ok": False,
                "version": VERSION,
                "locked": True,
                "lock_seconds": remaining,
                "lock_stage": int(row["lock_stage"] or 0),
                "note": "Η επαγγελματική πρόσβαση είναι προσωρινά κλειδωμένη.",
            }

        if not _verify_password(row, password):
            attempts = int(row["failed_attempts"] or 0) + 1
            stage = int(row["lock_stage"] or 0)
            lock_until = 0.0
            note = f"Λάθος κωδικός. Απομένουν {max(0, 5-attempts)} προσπάθειες."
            locked = False
            if attempts >= 5:
                locked = True
                attempts = 0
                if stage <= 0:
                    stage = 1
                    lock_until = _now() + FIRST_LOCK_SECONDS
                    note = "Έγιναν 5 λάθος προσπάθειες. Νέα προσπάθεια μετά από 30 λεπτά."
                else:
                    stage = 2
                    lock_until = _now() + SECOND_LOCK_SECONDS
                    note = "Έγιναν ξανά 5 λάθος προσπάθειες. Νέα προσπάθεια μετά από μία ημέρα."
            con.execute(
                """
                UPDATE accounts_v249382
                SET failed_attempts=?,lock_stage=?,lock_until=?,last_failed_at=?,updated_at=?
                WHERE room_id=? AND device_id=?
                """,
                (attempts, stage, lock_until, _now(), _now(), room_id, device_id),
            )
            _audit(con, actor_hash, "professional_unlock", False, "wrong-password")
            con.commit()
            return {
                "ok": False,
                "version": VERSION,
                "locked": locked,
                "lock_seconds": _remaining(lock_until),
                "lock_stage": stage,
                "failed_attempts": attempts,
                "note": note,
            }

        token = secrets.token_urlsafe(40)
        session_hash = _token_hash(token)
        expires_at = _now() + SESSION_SECONDS
        con.execute(
            """
            UPDATE accounts_v249382
            SET failed_attempts=0,lock_stage=0,lock_until=0,last_success_at=?,
                account_mode='professional',updated_at=?
            WHERE room_id=? AND device_id=?
            """,
            (_now(), _now(), room_id, device_id),
        )
        con.execute(
            """
            INSERT INTO professional_sessions_v249382(
              session_hash,room_id,device_id,created_at,expires_at,last_used_at,revoked_at
            ) VALUES(?,?,?,?,?,?,0)
            """,
            (session_hash, room_id, device_id, _now(), expires_at, _now()),
        )
        con.execute(
            """
            UPDATE professional_sessions_v249382
            SET revoked_at=?
            WHERE room_id=? AND device_id=? AND session_hash<>? AND revoked_at=0
            """,
            (_now(), room_id, device_id, session_hash),
        )
        _audit(con, actor_hash, "professional_unlock", True, "session-created")
        con.commit()
        return {
            **_public_account(_account(con, room_id, device_id), room_id, device_id),
            "session_token": token,
            "session_expires_at": expires_at,
            "note": "Η επαγγελματική πρόσβαση άνοιξε.",
        }


def _require_session(
    con: sqlite3.Connection,
    room_id: str,
    device_id: str,
    token: str,
) -> sqlite3.Row:
    token_hash = _token_hash(token)
    row = con.execute(
        """
        SELECT * FROM professional_sessions_v249382
        WHERE session_hash=? AND room_id=? AND device_id=?
          AND revoked_at=0 AND expires_at>?
        """,
        (token_hash, room_id, device_id, _now()),
    ).fetchone()
    if row is None:
        raise AccountError("Η επαγγελματική συνεδρία έληξε. Βάλε ξανά τον κωδικό.")
    con.execute(
        "UPDATE professional_sessions_v249382 SET last_used_at=? WHERE session_hash=?",
        (_now(), token_hash),
    )
    return row


def update_professional_profile(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    name = _validate_name(payload.get("account_name") or payload.get("nickname"))
    avatar = _validate_avatar(payload.get("avatar"))
    mode = _clean(payload.get("account_mode"), 30)
    if mode not in ("normal", "professional"):
        mode = "professional"

    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "professional_profile_update", limit=20, window_seconds=60 * 60
        )
        _require_session(con, room_id, device_id, str(payload.get("session_token") or ""))
        row = _account(con, room_id, device_id)
        if row is None or not bool(row["professional_enabled"]):
            raise AccountError("Δεν βρέθηκε επαγγελματικός λογαριασμός.")
        con.execute(
            """
            UPDATE accounts_v249382
            SET account_name=?,avatar=?,account_mode=?,updated_at=?
            WHERE room_id=? AND device_id=?
            """,
            (name, avatar, mode, _now(), room_id, device_id),
        )
        _audit(con, actor_hash, "professional_profile_update", True, "shared-profile")
        con.commit()
        return {
            **_public_account(_account(con, room_id, device_id), room_id, device_id),
            "note": "Το κοινό όνομα και η φωτογραφία ενημερώθηκαν.",
        }


def set_mode(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    mode = _clean(payload.get("account_mode"), 30)
    if mode not in ("normal", "professional"):
        return {"ok": False, "version": VERSION, "note": "Μη έγκυρος τύπος λογαριασμού."}

    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "account_mode", limit=30, window_seconds=60 * 60
        )
        if mode == "professional":
            _require_session(con, room_id, device_id, str(payload.get("session_token") or ""))
        row = _account(con, room_id, device_id)
        if row is None:
            raise AccountError("Δεν βρέθηκε λογαριασμός.")
        if mode == "professional" and not bool(row["professional_enabled"]):
            raise AccountError("Δεν έχει ενεργοποιηθεί επαγγελματικός λογαριασμός.")
        con.execute(
            """
            UPDATE accounts_v249382 SET account_mode=?,updated_at=?
            WHERE room_id=? AND device_id=?
            """,
            (mode, _now(), room_id, device_id),
        )
        _audit(con, actor_hash, "account_mode", True, mode)
        con.commit()
        return _public_account(_account(con, room_id, device_id), room_id, device_id)


def change_password(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    new_password = str(payload.get("new_password") or "")
    new_password_confirm = str(payload.get("new_password_confirm") or "")
    valid, note = _password_pair_valid(new_password, new_password_confirm)
    if not valid:
        return {"ok": False, "version": VERSION, "note": note}

    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "professional_password_change", limit=5, window_seconds=60 * 60
        )
        _require_session(con, room_id, device_id, str(payload.get("session_token") or ""))
        salt = secrets.token_bytes(24)
        salt_text = base64.urlsafe_b64encode(salt).decode("ascii")
        digest = _password_digest(new_password, salt)
        con.execute(
            """
            UPDATE accounts_v249382
            SET password_salt=?,password_hash=?,password_iterations=?,
                failed_attempts=0,lock_stage=0,lock_until=0,account_mode='normal',
                updated_at=?
            WHERE room_id=? AND device_id=?
            """,
            (salt_text, digest, PBKDF2_ITERATIONS, _now(), room_id, device_id),
        )
        con.execute(
            """
            UPDATE professional_sessions_v249382
            SET revoked_at=? WHERE room_id=? AND device_id=? AND revoked_at=0
            """,
            (_now(), room_id, device_id),
        )
        _audit(con, actor_hash, "professional_password_change", True, "all-sessions-revoked")
        con.commit()
        return {
            "ok": True,
            "version": VERSION,
            "note": "Ο κωδικός άλλαξε. Η επαγγελματική πρόσβαση έκλεισε και θα ζητηθεί ξανά.",
        }


def close_professional(payload: dict[str, Any]) -> dict[str, Any]:
    init_db()
    payload = dict(payload or {})
    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con, payload, "professional_close", limit=40, window_seconds=60 * 60
        )
        token = str(payload.get("session_token") or "")
        if token:
            con.execute(
                """
                UPDATE professional_sessions_v249382 SET revoked_at=?
                WHERE session_hash=? AND room_id=? AND device_id=?
                """,
                (_now(), _token_hash(token), room_id, device_id),
            )
        con.execute(
            """
            UPDATE accounts_v249382 SET account_mode='normal',updated_at=?
            WHERE room_id=? AND device_id=?
            """,
            (_now(), room_id, device_id),
        )
        _audit(con, actor_hash, "professional_close", True, "normal-mode")
        con.commit()
        return {
            **_public_account(_account(con, room_id, device_id), room_id, device_id),
            "note": "Η επαγγελματική πρόσβαση έκλεισε.",
        }


def validate_session(payload: dict[str, Any]) -> dict[str, Any]:
    """Validate the short professional session before opening one tool."""
    init_db()
    payload = dict(payload or {})
    with _conn() as con:
        room_id, device_id, actor_hash = _guard(
            con,
            payload,
            "professional_session_validate",
            limit=120,
            window_seconds=20 * 60,
        )
        row = _require_session(
            con,
            room_id,
            device_id,
            str(payload.get("session_token") or ""),
        )
        _audit(con, actor_hash, "professional_session_validate", True, "tool-open")
        con.commit()
        return {
            **_public_account(_account(con, room_id, device_id), room_id, device_id),
            "session_valid": True,
            "session_expires_at": float(row["expires_at"] or 0),
            "tools": ["V405", "V406", "V407", "V408", "V409", "V410", "V411"],
        }


def diagnostics() -> dict[str, Any]:
    init_db()
    with _conn() as con:
        accounts = con.execute("SELECT COUNT(*) c FROM accounts_v249382").fetchone()["c"]
        professional = con.execute(
            "SELECT COUNT(*) c FROM accounts_v249382 WHERE professional_enabled=1"
        ).fetchone()["c"]
        locked = con.execute(
            "SELECT COUNT(*) c FROM accounts_v249382 WHERE lock_until>?",
            (_now(),),
        ).fetchone()["c"]
        sessions = con.execute(
            """
            SELECT COUNT(*) c FROM professional_sessions_v249382
            WHERE revoked_at=0 AND expires_at>?
            """,
            (_now(),),
        ).fetchone()["c"]
        integrity = con.execute("PRAGMA integrity_check").fetchone()[0]
    return {
        "ok": integrity == "ok",
        "version": VERSION,
        "database_integrity": integrity,
        "accounts": int(accounts or 0),
        "professional_accounts": int(professional or 0),
        "currently_locked": int(locked or 0),
        "active_short_sessions": int(sessions or 0),
        "professional_center_v249383": True,
        "v405_to_v410_enabled": True,
        "v411_live_wall_enabled": True,
        "password_hash": "PBKDF2-HMAC-SHA256",
        "password_iterations": PBKDF2_ITERATIONS,
        "first_lock_seconds": FIRST_LOCK_SECONDS,
        "second_lock_seconds": SECOND_LOCK_SECONDS,
        "email_required": False,
        "phone_required": False,
        "plaintext_password_storage": False,
        "professional_password_required_each_entry": True,
        "friendship_required_for_dm_voice_video": True,
        "feed_requires_registered_member": True,
        "bot_honeypot": True,
        "request_replay_protection": True,
        "rate_limits": True,
    }


init_db()
