# *************************************************************************
#
#  Copyright (c) 2026 - Datatailr Inc.
#  All Rights Reserved.
#
#  This file is part of Datatailr and subject to the terms and conditions
#  defined in 'LICENSE.txt'. Unauthorized copying and/or distribution
#  of this file, in parts or full, via any medium is strictly prohibited.
# *************************************************************************

from __future__ import annotations
import json
from typing import Optional

from datatailr.wrapper import dt__User

# Datatailr User API Client
__client__ = dt__User()


class User:
    """
    Representing a Datatailr User.

    This class provides methods to interact with the Datatailr User API.
    It allows you to create, update, delete, and manage users within the Datatailr platform.

    Attributes:
        first_name (str): The first name of the user.
        last_name (str): The last name of the user.
        name (str): The username of the user.
        email (str): The email address of the user.
        user_id (int): The unique identifier for the user.
        groups (list): List of groups the user belongs to.
        is_system_user (bool): Indicates if the user is a system user.

    """

    def __init__(self, name: str | None = None, id: int | None = None):
        """Create a User instance by fetching user data from the platform.

        At least one of *name* or *id* must be provided.  When both are given,
        *name* takes precedence.

        Args:
            name: The username to look up.
            id: The numeric user ID to look up.

        Raises:
            ValueError: If neither *name* nor *id* is provided.
        """
        if name is None and id is None:
            raise ValueError(
                "Either 'name' or 'id' must be provided to initialize a User."
            )
        if name is not None and id is not None:
            print(
                "Warning: Both 'name' and 'id' provided. 'name' will be used to initialize the User."
            )
        self.__name: str | None = name
        self.__first_name: str | None = None
        self.__last_name: str | None = None
        self.__email: str | None = None
        self.__user_id: int | None = id
        self.__groups: list[str] = []
        self.__is_system_user = False
        self.__expiry__ = None
        self.__signature__ = None

        self.__refresh__()

    def __repr__(self):
        return (
            f"User(name={self.name}, first_name={self.first_name}, "
            f"last_name={self.last_name}, email={self.email}, "
            f"user_id={self.user_id}, groups={self.groups}, "
            f"is_system_user={self.is_system_user})"
        )

    def __str__(self):
        return f"<User: {self.name} | {self.user_id}>"

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return (
            self.user_id == other.user_id
            and self.name == other.name
            and self.email == other.email
            and self.groups == other.groups
            and self.is_system_user == other.is_system_user
            and self.first_name == other.first_name
            and self.last_name == other.last_name
        )

    def __refresh__(self) -> None:
        if self.name:
            user = __client__.get(self.name)
        else:
            user = __client__.get(id=self.user_id)
        if user:
            self.__name = user["name"]
            self.__first_name = user["first_name"]
            self.__last_name = user["last_name"]
            self.__email = user.get("email")
            self.__user_id = user["user_id"]
            self.__groups = [g for g in user.get("groups", "").split(",") if g]
            self.__is_system_user = user["is_system"]

    @property
    def first_name(self) -> str | None:
        """The first name of the user."""
        return self.__first_name

    @property
    def last_name(self) -> str | None:
        """The last name of the user."""
        return self.__last_name

    @property
    def name(self) -> str | None:
        """The username."""
        return self.__name

    @property
    def email(self) -> str | None:
        """The email address of the user."""
        return self.__email

    @property
    def user_id(self) -> int | None:
        """The unique numeric identifier of the user."""
        return self.__user_id

    @property
    def groups(self) -> list[str]:
        """List of group names the user belongs to."""
        return self.__groups

    @property
    def is_system_user(self) -> bool:
        """Whether this user is a system (non-interactive) user."""
        return self.__is_system_user

    def to_cookie(self) -> str:
        """Return a cookie string for the user."""
        self.verify()
        if self.__expiry__ is None or self.__signature__ is None:
            raise ValueError("User is not signed")
        return json.dumps(
            {
                "name": self.name,
                "expiry": self.__expiry__,
                "signature": self.__signature__,
            }
        )

    @staticmethod
    def get(name_or_id: str | int) -> User:
        """Retrieve an existing user by username or numeric ID.

        Args:
            name_or_id (str | int): The username (str) or user ID (int).

        Returns:
            The matching User instance.

        Raises:
            ValueError: If no user with the given name or ID exists.
        """
        if isinstance(name_or_id, int):
            user = next((u for u in User.ls() if u.user_id == name_or_id), None)
            if user is None:
                raise ValueError(f"User with ID {name_or_id} not found.")
            return user
        return User(name_or_id)

    @staticmethod
    def signed_user() -> User:
        """Retrieve the currently signed-in (authenticated) user.

        Returns:
            The User instance of the currently authenticated user.

        Raises:
            PermissionError: If no signed-in user is found.
        """
        user_signature_and_expiry = __client__.signed_user()
        if user_signature_and_expiry:
            user = User(name=user_signature_and_expiry["name"])
            user.__expiry__ = user_signature_and_expiry["expiry"]
            user.__signature__ = user_signature_and_expiry["signature"]
            return user

        raise PermissionError(
            "No signed user found. Please ensure you are signed in to Datatailr."
        )

    @staticmethod
    def add(
        name: str,
        first_name: str,
        last_name: str,
        email: str,
        password: str,
        groups: list[str] | None = None,
        is_system_user: bool = False,
    ) -> Optional["User"]:
        """Create a new user on the Datatailr platform.

        Args:
            name: The username for the new user.
            first_name: The user's first name.
            last_name: The user's last name.
            email: The user's email address.
            password: The login password. Ignored for system users.
            groups: Optional list of group names to add the user to.
            is_system_user: If ``True``, create a non-interactive system user.

        Returns:
            The newly created User, or ``None`` if the operation failed.

        Raises:
            Warning: If a password is provided for a system user.

        Notes:
            Passwords are ignored for system users.
        """
        if is_system_user:
            if password is not None:
                raise Warning(
                    "Password is not required for system users. It will be ignored."
                )
            new_user = __client__.add(
                name,
                first_name=first_name,
                last_name=last_name,
                email=email,
                groups=groups,
                system=is_system_user,
                json_enrichened=True,
            )
        else:
            new_user = __client__.add(
                name,
                first_name=first_name,
                last_name=last_name,
                email=email,
                password=password,
                groups=groups,
                system=is_system_user,
                json_enrichened=True,
            )

        return User(new_user["name"]) if new_user else None

    @staticmethod
    def exists(name: str) -> bool:
        """Check whether a user with the given username exists.

        Args:
            name: The username to look up.

        Returns:
            ``True`` if the user exists, ``False`` otherwise.
        """
        return __client__.exists(name)

    @staticmethod
    def ls() -> list:
        """List all users available in the Datatailr platform.

        Returns:
            list[User]: Users available in the platform.

        Examples:
            >>> from datatailr import User
            >>> User.ls()
            [...User(name=root, first_name=Super User, last_name=, email=None, user_id=0, groups=[], is_system_user=True), ...]
        """
        users = __client__.ls()
        return [User.get(user["name"]) for user in users]

    @staticmethod
    def remove(name: str) -> None:
        """Remove (delete) a user from the platform.

        Args:
            name: The username of the user to remove.
        """
        __client__.rm(name)
        return None

    def verify(self) -> None:
        """Verify the user's authentication signature with the platform.

        This refreshes and validates the user's credentials against the
        Datatailr authentication service.
        """
        signed_user = self.signed_user()
        if signed_user.name != self.name:
            raise ValueError(
                "Can only verify the currently signed-in user when the signed-in "
                "username matches this user's name."
            )
        self.__expiry__ = signed_user.__expiry__
        self.__signature__ = signed_user.__signature__
