# SPDX-FileCopyrightText: 2026 Martin Kirchgessner
#
# SPDX-License-Identifier: AGPL-3.0-or-later

from __future__ import annotations

import logging
from datetime import datetime

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
from sqlalchemy import Integer, String, delete, select
from sqlalchemy.dialects.sqlite import DATETIME, JSON
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm import Session as ormSession
from werkzeug.datastructures import MultiDict  # noqa: TC002

from radiotomate.db import Base
from radiotomate.enums import Role

_log = logging.getLogger(__name__)


class User(Base):
    """
    User info and roles
    """

    __tablename__ = "users"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    username: Mapped[str] = mapped_column(String, unique=True, nullable=False)
    password: Mapped[str] = mapped_column(String, nullable=False)
    notes: Mapped[str] = mapped_column(String, nullable=False, default="")
    created: Mapped[datetime] = mapped_column(DATETIME, default=datetime.now)
    modified: Mapped[datetime] = mapped_column(
        DATETIME,
        default=datetime.now,
        onupdate=datetime.now,
    )
    roles: Mapped[dict] = mapped_column(
        MutableDict.as_mutable(JSON),
        default={},
    )  # https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#sqlalchemy.dialects.sqlite.JSON

    sessions: Mapped[list[Session]] = relationship(  # noqa: F821
        lazy=True,
        back_populates="user",
        cascade="save-update, merge, expunge, delete",
    )

    @classmethod
    async def all(cls, session: ormSession) -> list[User]:
        return await session.scalars(select(User).order_by(User.username))

    @classmethod
    async def from_username(cls, session: ormSession, username: str) -> User | None:
        return await session.scalar(select(User).filter(User.username == username))

    @classmethod
    async def from_id(cls, session: ormSession, user_id: int) -> User | None:
        return await session.scalar(select(User).filter(User.id == user_id))

    @classmethod
    async def check(
        cls,
        session: ormSession,
        username: str,
        password: str,
    ) -> User | None:
        user = await cls.from_username(session, username)
        if user:
            ph = PasswordHasher()
            try:
                if ph.verify(user.password, password):
                    if ph.check_needs_rehash(user.password):
                        user.password = ph.hash(password)
                        await session.flush()
                    return user
            except VerifyMismatchError:
                return None
            except Exception as e:
                _log.exception(e)
        return None

    @classmethod
    async def delete(cls, session: ormSession, user_id: int):
        await session.execute(delete(User).where(User.id == user_id))

    def update_password(self, new_password):
        ph = PasswordHasher()
        self.password = ph.hash(new_password)

    def update_roles(self, form: MultiDict):
        roles = {}
        for role in Role:
            if form.get(f"can_{role}") == "true":
                roles[role] = True
        self.roles = roles

    def can_admin(self) -> bool:
        return self.roles.get("admin", False)

    def can_stream(self) -> bool:
        return self.can_admin() or self.roles.get("stream", False)

    def can_live(self) -> bool:
        return self.can_admin() or self.roles.get("live", False)

    def can_carts(self) -> bool:
        return self.can_admin() or self.roles.get("carts", False)

    def can_not_audio(self) -> bool:
        return self.roles.get("not_audio", False)

    def can_autodj(self) -> bool:
        return self.can_admin() or self.roles.get("autodj", False)


class LoggedOutUser:
    """
    This object fills current_user.user when the user is not authenticated.
    This simplifies authorization checks by providing all ``can_*()`` methods
    """

    def __init__(self):
        self.id = None
        self.username = None
        self.roles = {}

    def can_admin(self) -> bool:
        return False

    def can_stream(self) -> bool:
        return False

    def can_live(self) -> bool:
        return False

    def can_carts(self) -> bool:
        return False

    def can_not_audio(self) -> bool:
        return False

    def can_autodj(self) -> bool:
        return False
