import logging
from typing import Any, Optional, List, Dict

from httpx import HTTPStatusError
from pydantic import Field, BaseModel

from ipfabric.models import User
from ipfabric.tools.shared import TIMEZONES
from .rbac import Roles

logger = logging.getLogger("ipfabric")


class LocalUsers(BaseModel):
    client: Any = Field(exclude=True)
    _users: Optional[List[User]] = None
    _roles: Optional[Roles] = None

    def setup(self):
        self._users = self.get_users()
        try:
            self._roles = Roles(client=self.client)
        except HTTPStatusError:
            pass

    @property
    def users(self) -> List[User]:
        if not self._users:
            self.setup()
        return self._users

    @property
    def users_by_id(self) -> Dict[str, User]:
        return {_.user_id: _ for _ in self._users}

    def get_users(self, username: str = None) -> List[User]:
        """
        Gets all users or filters on one of the options.
        :param username: str: Username to filter
        :return: List of users
        """
        filters = {"username": ["ieq", username]} if username else None
        users = self.client.fetch_all("tables/users", filters=filters, snapshot=False)
        return [User(**user) for user in users]

    def get_user_by_id(self, user_id: str):
        """
        Gets a user by ID
        :param user_id: Union[str, int]: User ID to filter
        :return: User
        """
        resp = self.client.get("users/" + str(user_id))
        resp.raise_for_status()
        user = resp.json()
        return User(**user)

    def add_user(
        self,
        username: str,
        password: str,
        roles: list,
        timezone: str = "UTC",
    ):
        """
        Adds a user
        :param username: str: Username
        :param password: str: Must be 8 characters
        :param roles: list: Role IDs for Users
        :param timezone: str: v4.2 and above, Defaults UTC.  See pytz.all_timezones for correct syntax
        :return: User
        """
        if len(password) < 8:
            raise SyntaxError("Password must be 8 characters.")
        if self._roles and not all(x in [r.role_id for r in self._roles.roles] for x in roles):
            raise SyntaxError(f"Only accepted roles are {[r.role_id for r in self._roles.roles]}")
        payload = {
            "username": username,
            "password": password,
            "roleIds": roles,
        }
        if timezone.lower() not in TIMEZONES:
            raise ValueError(f"Timezone `{timezone}` is not located. Please see `ipfabric.tools.shared.TIMEZONES`.")
        payload["timezone"] = TIMEZONES[timezone.lower()]
        resp = self.client.post("users", json=payload)
        resp.raise_for_status()
        self._users = self.get_users()
        return self.users_by_id[resp.json()["id"]]

    def delete_user(self, user_id: str) -> bool:
        """
        Deletes a user and returns list of remaining users
        :param user_id:
        :return:
        """
        resp = self.client.delete("users/" + str(user_id))
        resp.raise_for_status()
        self._users = self.get_users()
        return True
