"""Unguessable deep-link token lifecycle."""

from __future__ import annotations

import logging
import secrets
from dataclasses import dataclass
from datetime import datetime

from app.database.base import utcnow
from app.database.models import AccessToken, Post
from app.database.repositories import Repositories
from app.errors import NotFoundError, TokenError, ValidationError
from app.utils.validation import validate_link_template

logger = logging.getLogger(__name__)


@dataclass(frozen=True, slots=True)
class TokenValidation:
    valid: bool
    reason: str
    access_token: AccessToken | None = None
    post: Post | None = None


class TokenService:
    def __init__(self, repos: Repositories) -> None:
        self.repos = repos

    async def build_link(self, token: str, post: Post | None = None) -> str:
        username = await self.repos.settings.get("bot_username")
        username = (username or "").strip().lstrip("@")
        template = (
            post.video_link_template_override
            if post is not None and post.video_link_template_override
            else await self.repos.settings.get(
                "video_link_template", "https://t.me/{bot_username}?start={token}"
            )
        )
        template = validate_link_template(template)
        if "{bot_username}" in template and not username:
            raise ValidationError(
                "نام کاربری ربات هنوز ثبت نشده است. BOT_USERNAME را تنظیم کنید یا ربات را یک‌بار اجرا کنید."
            )
        return template.replace("{bot_username}", username).replace("{token}", token)

    async def ensure_for_post(
        self,
        post_id: int,
        *,
        expires_at: datetime | None = None,
        max_uses: int | None = None,
    ) -> tuple[AccessToken, str]:
        post = await self.repos.posts.get(post_id)
        if post is None:
            raise NotFoundError("پست پیدا نشد.")
        token = await self.repos.tokens.active_for_post(post_id)
        if token is not None:
            expired = token.expires_at is not None and token.expires_at <= utcnow()
            exhausted = token.max_uses is not None and token.use_count >= token.max_uses
            if expired or exhausted:
                await self.repos.tokens.update(token.id, is_active=False)
                token = None
        if token is None:
            raw = await self._new_token_value()
            token = await self.repos.tokens.create(
                post_id=post_id,
                token=raw,
                expires_at=expires_at,
                max_uses=max_uses,
            )
        link = await self.build_link(token.token, post)
        if post.video_link != link:
            await self.repos.posts.update(post_id, video_link=link)
        return token, link

    async def _new_token_value(self) -> str:
        for _ in range(5):
            candidate = secrets.token_urlsafe(32)
            if len(candidate) > 64:
                candidate = candidate[:64]
            if await self.repos.tokens.get_by_token(candidate) is None:
                return candidate
        raise TokenError("تولید توکن یکتا ناموفق بود.")

    async def validate(self, raw_token: str, *, consume: bool = False) -> TokenValidation:
        token = await self.repos.tokens.get_by_token(raw_token, with_post=True)
        if token is None:
            return TokenValidation(False, "invalid")
        if not token.is_active:
            return TokenValidation(False, "inactive", token, token.post)
        now = utcnow()
        if token.expires_at is not None and token.expires_at <= now:
            return TokenValidation(False, "expired", token, token.post)
        if token.max_uses is not None and token.use_count >= token.max_uses:
            return TokenValidation(False, "exhausted", token, token.post)
        if consume and not await self.repos.tokens.consume(token.id, now=now):
            return TokenValidation(False, "exhausted", token, token.post)
        return TokenValidation(True, "ok", token, token.post)

    async def regenerate(
        self,
        post_id: int,
        *,
        expires_at: datetime | None = None,
        max_uses: int | None = None,
        actor_id: int | None = None,
    ) -> str:
        post = await self.repos.posts.get(post_id)
        if post is None:
            raise NotFoundError("پست پیدا نشد.")
        await self.repos.tokens.deactivate_for_post(post_id)
        raw = await self._new_token_value()
        await self.repos.tokens.create(
            post_id=post_id,
            token=raw,
            expires_at=expires_at,
            max_uses=max_uses,
        )
        link = await self.build_link(raw, post)
        await self.repos.posts.update(post_id, video_link=link)
        await self.repos.audit.log(
            actor_id,
            "access_token.regenerate",
            entity_type="post",
            entity_id=post_id,
        )
        return link

    async def set_active(self, token_id: int, active: bool, actor_id: int | None = None) -> bool:
        changed = await self.repos.tokens.update(token_id, is_active=active)
        if changed:
            await self.repos.audit.log(
                actor_id,
                "access_token.set_active",
                entity_type="access_token",
                entity_id=token_id,
                details={"active": active},
            )
        return changed

    async def set_limits(
        self,
        token_id: int,
        *,
        expires_at: datetime | None,
        max_uses: int | None,
        actor_id: int | None = None,
    ) -> bool:
        changed = await self.repos.tokens.update(
            token_id, expires_at=expires_at, max_uses=max_uses
        )
        if changed:
            await self.repos.audit.log(
                actor_id,
                "access_token.set_limits",
                entity_type="access_token",
                entity_id=token_id,
                details={"has_expiry": expires_at is not None, "max_uses": max_uses},
            )
        return changed
