from collections import defaultdict
from dataclasses import dataclass, field
from typing import Any

from dataclasses_json import DataClassJsonMixin, config

ATTR_NAME = "attributes"  # Name of the "attributes" attribute


def decode_facility(data: dict[str, Any]) -> "PerunFacility":
    return PerunFacility.from_dict(data.pop(ATTR_NAME, {}) | data)


def decode_groups(data: dict[str, Any]) -> list["PerunGroup"]:
    return [PerunGroup.from_dict({"@id": k} | v.pop(ATTR_NAME, {}) | v) for k, v in data.items()]


def decode_users(data: dict[str, Any]) -> list["PerunUser"]:
    return [PerunUser.from_dict({"@id": k} | v.pop(ATTR_NAME, {}) | v) for k, v in data.items()]


def decode_resources(data: dict[str, Any]) -> list["PerunResource"]:
    return [PerunResource.from_dict({"@id": k} | v.pop(ATTR_NAME, {}) | v) for k, v in data.items()]


def lambda_keys_decoder(x: dict[str, Any]) -> list[str]:
    return list(x.keys())


@dataclass
class PerunFacility(DataClassJsonMixin):
    name: str = field(metadata=config(field_name="facilityName"))
    uuid: str = field(metadata=config(field_name="facilityUuid"))
    destinations: list[str] = field(default_factory=list)


@dataclass
class PerunMetadata(DataClassJsonMixin):
    version: str
    facility: PerunFacility = field(metadata=config(decoder=decode_facility))


@dataclass
class PerunGroup(DataClassJsonMixin):
    id: str = field(metadata=config(field_name="@id"))
    name: str = field(metadata=config(field_name="urn:perun:group:attribute-def:core:name"))
    # noinspection SpellCheckingInspection
    inet_cispr: str | None = field(
        default=None, metadata=config(field_name="urn:perun:group:attribute-def:def:inetCispr")
    )


@dataclass
class PerunResource(DataClassJsonMixin):
    id: str = field(metadata=config(field_name="@id"))
    name: str | None = field(metadata=config(field_name="urn:perun:resource:attribute-def:core:name"), default=None)
    capabilities: list[str] = field(
        metadata=config(field_name="urn:perun:resource:attribute-def:def:capabilities"), default_factory=list
    )
    #
    internal_assigned_groups_list: list[str] = field(
        default_factory=list, metadata=config(field_name="assigned_groups", decoder=lambda_keys_decoder)
    )
    #
    assigned_groups: list[PerunGroup] = field(default_factory=list)


@dataclass
class PerunUser(DataClassJsonMixin):
    id: str = field(metadata=config(field_name="@id"))
    login_mu: str = field(metadata=config(field_name="urn:perun:user:attribute-def:def:login-namespace:mu"))
    scoped_login_mu: str = field(
        metadata=config(field_name="urn:perun:user:attribute-def:virt:scopedLogin-namespace:mu")
    )
    display_name: str = field(metadata=config(field_name="urn:perun:user:attribute-def:core:displayName"))
    preferred_mail: str = field(metadata=config(field_name="urn:perun:user:attribute-def:def:preferredMail"))
    #
    internal_allowed_groups_list: list[str] = field(
        default_factory=list, metadata=config(field_name="allowed_groups", decoder=lambda_keys_decoder)
    )
    internal_allowed_resources_list: list[str] = field(
        default_factory=list, metadata=config(field_name="allowed_resources", decoder=lambda_keys_decoder)
    )
    #
    allowed_groups: list[PerunGroup] = field(default_factory=list)
    allowed_resources: list[PerunResource] = field(default_factory=list)
    aliases: list[str] = field(default_factory=list)


@dataclass
class PerunPropagation(DataClassJsonMixin):
    metadata: PerunMetadata
    groups: list[PerunGroup] = field(default_factory=list, metadata=config(decoder=decode_groups))
    resources: list[PerunResource] = field(default_factory=list, metadata=config(decoder=decode_resources))
    users: list[PerunUser] = field(default_factory=list, metadata=config(decoder=decode_users))

    def __post_init__(self) -> None:
        self._update_base_relationships()

    def _update_base_relationships(self: "PerunPropagation") -> None:
        groups_map: dict[str, PerunGroup] = {x.id: x for x in self.groups}

        r: PerunResource
        for r in self.resources:
            r.assigned_groups = [groups_map[k] for k in r.internal_assigned_groups_list]

        resources_map: dict[str, PerunResource] = {x.id: x for x in self.resources}

        u: PerunUser
        for u in self.users:
            u.allowed_groups = [groups_map[k] for k in u.internal_allowed_groups_list]
            u.allowed_resources = [resources_map[k] for k in u.internal_allowed_resources_list]

    def get_groups_mapping(self: "PerunPropagation") -> dict[str, list[PerunUser]]:
        result: dict[str, list[PerunUser]] = defaultdict(list)

        for u in self.users:
            for g in u.allowed_groups:
                result[g.name].append(u)

        return result

    def get_capabilities_mapping(self: "PerunPropagation") -> dict[str, list[PerunUser]]:
        result: dict[str, list[PerunUser]] = defaultdict(list)

        for u in self.users:
            for r in u.allowed_resources:
                for c in r.capabilities:
                    result[c].append(u)

        return result
