"""
JSON file storage for Telegram user mappings and pending verification codes.

Thread-safe file operations with atomic writes.
"""

import json
import logging
import os
import secrets
import string
import tempfile
import time

from . import config

logger = logging.getLogger(__name__)


def _read_json(path: str) -> dict:
    """Read a JSON file, return empty dict if not found or invalid."""
    try:
        with open(path, "r") as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}


def _write_json(path: str, data: dict) -> None:
    """Atomically write JSON data to file."""
    dir_path = os.path.dirname(path)
    os.makedirs(dir_path, exist_ok=True)

    fd, tmp_path = tempfile.mkstemp(dir=dir_path, suffix=".tmp")
    try:
        with os.fdopen(fd, "w") as f:
            json.dump(data, f, indent=2)
        os.chmod(tmp_path, 0o660)  # group-readable for data-ops (webapp)
        os.replace(tmp_path, path)
    except Exception:
        os.unlink(tmp_path)
        raise


# --- Telegram Users ---


def get_chat_id(username: str) -> int | None:
    """Get Telegram chat_id for a linked username."""
    users = _read_json(config.TELEGRAM_USERS_FILE)
    entry = users.get(username)
    if entry:
        return entry.get("chat_id")
    return None


def get_username_by_chat_id(chat_id: int) -> str | None:
    """Reverse lookup: get username for a Telegram chat_id."""
    users = _read_json(config.TELEGRAM_USERS_FILE)
    for username, entry in users.items():
        if entry.get("chat_id") == chat_id:
            return username
    return None


def link_user(username: str, chat_id: int) -> None:
    """Link a username to a Telegram chat_id."""
    users = _read_json(config.TELEGRAM_USERS_FILE)
    users[username] = {
        "chat_id": chat_id,
        "linked_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    }
    _write_json(config.TELEGRAM_USERS_FILE, users)
    logger.info(f"Linked user '{username}' to chat_id {chat_id}")


def unlink_user(username: str) -> bool:
    """Unlink a username from Telegram. Returns True if was linked."""
    users = _read_json(config.TELEGRAM_USERS_FILE)
    if username in users:
        del users[username]
        _write_json(config.TELEGRAM_USERS_FILE, users)
        logger.info(f"Unlinked user '{username}'")
        return True
    return False


def get_user_status(username: str) -> dict | None:
    """Get link status for a username. Returns dict with linked_at or None."""
    users = _read_json(config.TELEGRAM_USERS_FILE)
    return users.get(username)


# --- Verification Codes ---


def _generate_code() -> str:
    """Generate a cryptographically random numeric verification code.

    Uses `secrets.choice` (CSPRNG) rather than `random.choices` because
    pairing codes gate account linkage — a predictable PRNG output would
    let an attacker who scrapes one code recover the RNG state and predict
    others issued in the same process.
    """
    return "".join(secrets.choice(string.digits) for _ in range(config.CODE_LENGTH))


def _cleanup_expired(codes: dict) -> dict:
    """Remove expired codes."""
    now = time.time()
    return {code: data for code, data in codes.items() if now - data.get("created_at", 0) < config.CODE_TTL_SECONDS}


def create_verification_code(chat_id: int) -> str:
    """Create a new verification code for a Telegram chat_id."""
    codes = _read_json(config.PENDING_CODES_FILE)
    codes = _cleanup_expired(codes)

    # Remove any existing code for this chat_id
    codes = {code: data for code, data in codes.items() if data.get("chat_id") != chat_id}

    code = _generate_code()
    # Ensure uniqueness
    while code in codes:
        code = _generate_code()

    codes[code] = {
        "chat_id": chat_id,
        "created_at": time.time(),
    }
    _write_json(config.PENDING_CODES_FILE, codes)
    logger.info(f"Created verification code for chat_id {chat_id}")
    return code


def verify_code(code: str) -> int | None:
    """Verify a code and return the chat_id if valid. Consumes the code."""
    codes = _read_json(config.PENDING_CODES_FILE)
    codes = _cleanup_expired(codes)

    data = codes.get(code)
    if data is None:
        return None

    chat_id = data["chat_id"]

    # Consume the code
    del codes[code]
    _write_json(config.PENDING_CODES_FILE, codes)

    logger.info(f"Verified code for chat_id {chat_id}")
    return chat_id
