"""Nexus — Admin & LoginAttempt Repository (Async SQLAlchemy)"""

from typing import Optional
from sqlalchemy import select, func, delete
from sqlalchemy.ext.asyncio import AsyncSession

from server.domain.models import Admin, LoginAttempt


class AdminRepositoryImpl:
    def __init__(self, session: AsyncSession):
        self.session = session

    async def get_by_id(self, id: int) -> Optional[Admin]:
        result = await self.session.execute(select(Admin).where(Admin.id == id))
        return result.scalar_one_or_none()

    async def get_by_username(self, username: str) -> Optional[Admin]:
        result = await self.session.execute(select(Admin).where(Admin.username == username))
        return result.scalar_one_or_none()

    async def create(self, admin: Admin) -> Admin:
        self.session.add(admin)
        await self.session.commit()
        return admin

    async def update(self, admin: Admin) -> Admin:
        await self.session.commit()
        return admin


class LoginAttemptRepositoryImpl:
    def __init__(self, session: AsyncSession):
        self.session = session

    async def create(self, attempt: LoginAttempt) -> LoginAttempt:
        self.session.add(attempt)
        await self.session.commit()
        return attempt

    async def count_recent_failures(self, username: str, ip_address: str, minutes: int = 15) -> int:
        from datetime import datetime, timedelta, timezone
        cutoff = datetime.now(timezone.utc) - timedelta(minutes=minutes)
        # Lock by username (primary) OR by IP (secondary) — whichever is over threshold.
        # Using IP-only for counting means an attacker can rotate IPs to bypass;
        # we count all IPs that attempted this username so rotating IPs doesn't help.
        result = await self.session.execute(
            select(func.count(LoginAttempt.id))
            .where(LoginAttempt.username == username)
            .where(LoginAttempt.success == False)
            .where(LoginAttempt.attempted_at >= cutoff)
        )
        return result.scalar_one() or 0

    async def clear_failures_for_username(self, username: str) -> int:
        result = await self.session.execute(
            delete(LoginAttempt)
            .where(LoginAttempt.username == username)
            .where(LoginAttempt.success == False)
        )
        await self.session.commit()
        return result.rowcount or 0