"""DM pairing policy: gate unknown senders on messaging channels."""

from __future__ import annotations

import logging
import secrets
from datetime import UTC, datetime, timedelta

import aiosqlite

log = logging.getLogger(__name__)

PAIRING_CODE_LENGTH = 6
PAIRING_EXPIRY_MINUTES = 10


class PairingService:
    def __init__(self, db: aiosqlite.Connection) -> None:
        self._db = db

    async def get_dm_policy(self) -> str:
        """Get the global DM policy. Returns 'pairing', 'allowlist', or 'open'."""
        # Read from settings table or return default
        try:
            async with self._db.execute(
                "SELECT value FROM settings WHERE key = 'dm_policy'"
            ) as cur:
                row = await cur.fetchone()
                return row[0] if row else "pairing"
        except Exception:
            return "pairing"

    async def set_dm_policy(self, policy: str) -> None:
        """Set the global DM policy."""
        await self._db.execute(
            "INSERT OR REPLACE INTO settings (key, value) VALUES ('dm_policy', ?)",
            (policy,),
        )
        await self._db.commit()

    async def is_allowed(self, channel: str, sender_id: str) -> bool:
        """Check if a sender is in the allowlist."""
        async with self._db.execute(
            "SELECT 1 FROM dm_allowlist WHERE channel = ? AND sender_id = ?",
            (channel, sender_id),
        ) as cur:
            return await cur.fetchone() is not None

    async def add_to_allowlist(self, channel: str, sender_id: str) -> None:
        """Add a sender to the allowlist."""
        now = datetime.now(UTC).isoformat()
        await self._db.execute(
            "INSERT OR IGNORE INTO dm_allowlist (channel, sender_id, approved_at) VALUES (?, ?, ?)",
            (channel, sender_id, now),
        )
        await self._db.commit()

    async def remove_from_allowlist(self, channel: str, sender_id: str) -> None:
        """Remove a sender from the allowlist."""
        await self._db.execute(
            "DELETE FROM dm_allowlist WHERE channel = ? AND sender_id = ?",
            (channel, sender_id),
        )
        await self._db.commit()

    async def get_allowlist(self) -> list[dict]:
        """Get all allowlist entries."""
        async with self._db.execute(
            "SELECT channel, sender_id, approved_at FROM dm_allowlist ORDER BY approved_at DESC"
        ) as cur:
            return [dict(r) for r in await cur.fetchall()]

    async def generate_pairing_code(self, channel: str, sender_id: str) -> str:
        """Generate a pairing code for an unknown sender. Idempotent — returns existing if pending."""
        now = datetime.now(UTC)

        # Check for existing pending code
        async with self._db.execute(
            "SELECT code FROM dm_pairing WHERE channel = ? AND sender_id = ? AND status = 'pending' AND expires_at > ?",
            (channel, sender_id, now.isoformat()),
        ) as cur:
            row = await cur.fetchone()
            if row:
                return row[0]

        # Generate new code
        code = secrets.token_hex(3).upper()  # 6 hex chars
        expires = now + timedelta(minutes=PAIRING_EXPIRY_MINUTES)
        await self._db.execute(
            "INSERT INTO dm_pairing (channel, sender_id, code, created_at, expires_at, status) VALUES (?, ?, ?, ?, ?, 'pending')",
            (channel, sender_id, code, now.isoformat(), expires.isoformat()),
        )
        await self._db.commit()
        log.info("Pairing code generated: %s for %s/%s", code, channel, sender_id)
        return code

    async def get_pending(self) -> list[dict]:
        """Get pending pairing requests."""
        now = datetime.now(UTC).isoformat()
        # Clean up expired first
        await self._db.execute(
            "UPDATE dm_pairing SET status = 'expired' WHERE status = 'pending' AND expires_at < ?",
            (now,),
        )
        await self._db.commit()

        async with self._db.execute(
            "SELECT channel, sender_id, code, created_at, expires_at FROM dm_pairing WHERE status = 'pending' ORDER BY created_at DESC"
        ) as cur:
            return [dict(r) for r in await cur.fetchall()]

    async def approve(self, channel: str, sender_id: str) -> bool:
        """Approve a pairing request. Adds sender to allowlist."""
        async with self._db.execute(
            "SELECT id FROM dm_pairing WHERE channel = ? AND sender_id = ? AND status = 'pending'",
            (channel, sender_id),
        ) as cur:
            if not await cur.fetchone():
                return False

        await self._db.execute(
            "UPDATE dm_pairing SET status = 'approved' WHERE channel = ? AND sender_id = ? AND status = 'pending'",
            (channel, sender_id),
        )
        await self.add_to_allowlist(channel, sender_id)
        log.info("Pairing approved: %s/%s", channel, sender_id)
        return True

    async def reject(self, channel: str, sender_id: str) -> bool:
        """Reject a pairing request."""
        result = await self._db.execute(
            "UPDATE dm_pairing SET status = 'rejected' WHERE channel = ? AND sender_id = ? AND status = 'pending'",
            (channel, sender_id),
        )
        await self._db.commit()
        return result.rowcount > 0
