"""UniFi Protect User models."""

from __future__ import annotations

from datetime import datetime
from functools import cache
from typing import Any

from pydantic.v1.fields import PrivateAttr

from .base import ProtectBaseObject, ProtectModel, ProtectModelWithId
from .types import ModelType, PermissionNode


class Permission(ProtectBaseObject):
    raw_permission: str
    model: ModelType
    nodes: set[PermissionNode]
    obj_ids: set[str] | None

    @classmethod
    def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
        permission = data.get("rawPermission", "")
        parts = permission.split(":")
        if len(parts) < 2:
            raise ValueError(f"Invalid permission: {permission}")

        data["model"] = ModelType(parts[0])
        if parts[1] == "*":
            data["nodes"] = list(PermissionNode)
        else:
            data["nodes"] = [PermissionNode(n) for n in parts[1].split(",")]

        if len(parts) == 3 and parts[2] != "*":
            if parts[2] == "$":
                data["obj_ids"] = ["self"]
            else:
                data["obj_ids"] = parts[2].split(",")

        return super().unifi_dict_to_dict(data)

    def unifi_dict(  # type: ignore[override]
        self,
        data: dict[str, Any] | None = None,
        exclude: set[str] | None = None,
    ) -> str:
        return self.raw_permission

    @property
    def objs(self) -> list[ProtectModelWithId] | None:
        if self.obj_ids == {"self"} or self.obj_ids is None:
            return None

        devices = getattr(self._api.bootstrap, self.model.devices_key)
        return [devices[oid] for oid in self.obj_ids]


class Group(ProtectModelWithId):
    name: str
    permissions: list[Permission]
    type: str
    is_default: bool

    @classmethod
    def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
        if "permissions" in data:
            permissions = data.pop("permissions")
            data["permissions"] = [{"rawPermission": p} for p in permissions]

        return super().unifi_dict_to_dict(data)


class UserLocation(ProtectModel):
    is_away: bool
    latitude: float | None
    longitude: float | None


class CloudAccount(ProtectModelWithId):
    first_name: str
    last_name: str
    email: str
    user_id: str
    name: str
    location: UserLocation | None
    profile_img: str | None = None

    @classmethod
    @cache
    def _get_unifi_remaps(cls) -> dict[str, str]:
        return {**super()._get_unifi_remaps(), "user": "userId"}

    def unifi_dict(
        self,
        data: dict[str, Any] | None = None,
        exclude: set[str] | None = None,
    ) -> dict[str, Any]:
        data = super().unifi_dict(data=data, exclude=exclude)

        # id and cloud ID are always the same
        if "id" in data:
            data["cloudId"] = data["id"]
        if "location" in data and data["location"] is None:
            del data["location"]

        return data

    @property
    def user(self) -> User:
        return self._api.bootstrap.users[self.user_id]


class UserFeatureFlags(ProtectBaseObject):
    notifications_v2: bool


class User(ProtectModelWithId):
    permissions: list[Permission]
    last_login_ip: str | None
    last_login_time: datetime | None
    is_owner: bool
    enable_notifications: bool
    has_accepted_invite: bool
    all_permissions: list[Permission]
    scopes: list[str] | None = None
    location: UserLocation | None
    name: str
    first_name: str
    last_name: str
    email: str | None
    local_username: str
    group_ids: list[str]
    cloud_account: CloudAccount | None
    feature_flags: UserFeatureFlags

    # TODO:
    # settings
    # alertRules
    # notificationsV2
    # notifications
    # cloudProviders

    _groups: list[Group] | None = PrivateAttr(None)
    _perm_cache: dict[str, bool] = PrivateAttr({})

    def __init__(self, **data: Any) -> None:
        if "permissions" in data:
            permissions = data.pop("permissions")
            data["permissions"] = [
                {"raw_permission": p} if isinstance(p, str) else p for p in permissions
            ]
        if "allPermissions" in data:
            permissions = data.pop("allPermissions")
            data["allPermissions"] = [
                {"raw_permission": p} if isinstance(p, str) else p for p in permissions
            ]

        super().__init__(**data)

    @classmethod
    def unifi_dict_to_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
        if "permissions" in data:
            permissions = data.pop("permissions")
            data["permissions"] = [{"rawPermission": p} for p in permissions]
        if "allPermissions" in data:
            permissions = data.pop("allPermissions")
            data["allPermissions"] = [{"rawPermission": p} for p in permissions]

        return super().unifi_dict_to_dict(data)

    @classmethod
    @cache
    def _get_unifi_remaps(cls) -> dict[str, str]:
        return {**super()._get_unifi_remaps(), "groups": "groupIds"}

    def unifi_dict(
        self,
        data: dict[str, Any] | None = None,
        exclude: set[str] | None = None,
    ) -> dict[str, Any]:
        data = super().unifi_dict(data=data, exclude=exclude)

        if "location" in data and data["location"] is None:
            del data["location"]

        return data

    @property
    def groups(self) -> list[Group]:
        """
        Groups the user is in

        Will always be empty if the user only has read only access.
        """
        if self._groups is not None:
            return self._groups

        self._groups = [
            self._api.bootstrap.groups[g]
            for g in self.group_ids
            if g in self._api.bootstrap.groups
        ]
        return self._groups

    def can(
        self,
        model: ModelType,
        node: PermissionNode,
        obj: ProtectModelWithId | None = None,
    ) -> bool:
        """Checks if a user can do a specific action"""
        check_self = False
        if model is self.model and obj is not None and obj.id == self.id:
            perm_str = f"{model.value}:{node.value}:$"
            check_self = True
        else:
            perm_str = (
                f"{model.value}:{node.value}:{obj.id if obj is not None else '*'}"
            )
        if perm_str in self._perm_cache:
            return self._perm_cache[perm_str]

        for perm in self.all_permissions:
            if model is not perm.model or node not in perm.nodes:
                continue
            if perm.obj_ids is None:
                self._perm_cache[perm_str] = True
                return True
            if check_self and perm.obj_ids == {"self"}:
                self._perm_cache[perm_str] = True
                return True
            if perm.obj_ids is not None and obj is not None and obj.id in perm.obj_ids:
                self._perm_cache[perm_str] = True
                return True
        self._perm_cache[perm_str] = False
        return False
