from typing import TYPE_CHECKING, Annotated

import strawberry
import strawberry_django
from django.db.models import Func, IntegerField

from circuits.models import CircuitTermination
from core.graphql.mixins import ChangelogMixin
from dcim import models
from extras.graphql.mixins import ConfigContextMixin, ContactsMixin, ImageAttachmentsMixin
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
from netbox.graphql.optimization import build_gfk_prefetch
from netbox.graphql.scalars import BigInt
from netbox.graphql.types import (
    BaseObjectType,
    NestedGroupObjectType,
    NetBoxObjectType,
    OrganizationalObjectType,
    PrimaryObjectType,
)
from users.graphql.mixins import OwnerMixin
from utilities.querysets import RestrictedPrefetch
from virtualization.models import Cluster, VMInterface

from .filters import *
from .mixins import CabledObjectMixin, PathEndpointMixin

if TYPE_CHECKING:
    from circuits.graphql.types import CircuitTerminationType
    from extras.graphql.types import ConfigTemplateType
    from ipam.graphql.types import (
        ASNType,
        IPAddressType,
        PrefixType,
        ServiceType,
        VLANTranslationPolicyType,
        VLANType,
        VRFType,
    )
    from tenancy.graphql.types import TenantType
    from users.graphql.types import UserType
    from virtualization.graphql.types import ClusterType, VirtualMachineType, VMInterfaceType
    from vpn.graphql.types import L2VPNTerminationType
    from wireless.graphql.types import WirelessLANType, WirelessLinkType

__all__ = (
    'CableBundleType',
    'CableType',
    'ComponentType',
    'ConsolePortTemplateType',
    'ConsolePortType',
    'ConsoleServerPortTemplateType',
    'ConsoleServerPortType',
    'DeviceBayTemplateType',
    'DeviceBayType',
    'DeviceRoleType',
    'DeviceType',
    'DeviceTypeType',
    'FrontPortTemplateType',
    'FrontPortType',
    'InterfaceTemplateType',
    'InterfaceType',
    'InventoryItemRoleType',
    'InventoryItemTemplateType',
    'InventoryItemType',
    'LocationType',
    'MACAddressType',
    'ManufacturerType',
    'ModularComponentType',
    'ModuleBayTemplateType',
    'ModuleBayType',
    'ModuleType',
    'ModuleTypeProfileType',
    'ModuleTypeType',
    'PlatformType',
    'PowerFeedType',
    'PowerOutletTemplateType',
    'PowerOutletType',
    'PowerPanelType',
    'PowerPortTemplateType',
    'PowerPortType',
    'RackGroupType',
    'RackReservationType',
    'RackRoleType',
    'RackType',
    'RackTypeType',
    'RearPortTemplateType',
    'RearPortType',
    'RegionType',
    'SiteGroupType',
    'SiteType',
    'VirtualChassisType',
    'VirtualDeviceContextType',
)


#
# Base types
#


@strawberry.type
class ComponentType(OwnerMixin, NetBoxObjectType):
    """
    Base type for device/VM components
    """
    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]


@strawberry.type
class ModularComponentType(ComponentType):
    module: Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')] | None


@strawberry.type
class ComponentTemplateType(ChangelogMixin, BaseObjectType):
    """
    Base type for device/VM components
    """
    device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]


@strawberry.type
class ModularComponentTemplateType(ComponentTemplateType):
    """
    Base type for ComponentTemplateModel which supports optional assignment to a ModuleType.
    """
    device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')] | None
    module_type: Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')] | None

#
# Model types
#


@strawberry_django.type(
    models.CableBundle,
    fields='__all__',
    filters=CableBundleFilter,
    pagination=True
)
class CableBundleType(PrimaryObjectType):
    cables: list[Annotated['CableType', strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.CableTermination,
    exclude=['termination_type', 'termination_id', '_device', '_rack', '_location', '_site'],
    filters=CableTerminationFilter,
    pagination=True
)
class CableTerminationType(NetBoxObjectType):
    cable: Annotated['CableType', strawberry.lazy('dcim.graphql.types')] | None

    @strawberry_django.field(
        prefetch_related=build_gfk_prefetch(
            'termination',
            [
                CircuitTermination,
                models.ConsolePort,
                models.ConsoleServerPort,
                models.FrontPort,
                models.Interface,
                models.PowerFeed,
                models.PowerOutlet,
                models.PowerPort,
                models.RearPort,
            ],
        ),
        only=['termination_type', 'termination_id'],
    )
    def termination(self) -> Annotated[
        Annotated['CircuitTerminationType', strawberry.lazy('circuits.graphql.types')]
        | Annotated['ConsolePortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['ConsoleServerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['FrontPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerFeedType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerOutletType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['RearPortType', strawberry.lazy('dcim.graphql.types')],
        strawberry.union('CableTerminationTerminationType'),
    ] | None:
        return self.termination


@strawberry_django.type(
    models.Cable,
    fields='__all__',
    filters=CableFilter,
    pagination=True
)
class CableType(PrimaryObjectType):
    color: str
    tenant: Annotated['TenantType', strawberry.lazy('tenancy.graphql.types')] | None
    bundle: Annotated['CableBundleType', strawberry.lazy('dcim.graphql.types')] | None

    terminations: list[CableTerminationType]

    a_terminations: list[Annotated[
        Annotated['CircuitTerminationType', strawberry.lazy('circuits.graphql.types')]
        | Annotated['ConsolePortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['ConsoleServerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['FrontPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerFeedType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerOutletType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['RearPortType', strawberry.lazy('dcim.graphql.types')],
        strawberry.union('CableTerminationTerminationType'),
    ]]

    b_terminations: list[Annotated[
        Annotated['CircuitTerminationType', strawberry.lazy('circuits.graphql.types')]
        | Annotated['ConsolePortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['ConsoleServerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['FrontPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerFeedType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerOutletType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['RearPortType', strawberry.lazy('dcim.graphql.types')],
        strawberry.union('CableTerminationTerminationType'),
    ]]


@strawberry_django.type(
    models.ConsolePort,
    exclude=['_path'],
    filters=ConsolePortFilter,
    pagination=True
)
class ConsolePortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
    pass


@strawberry_django.type(
    models.ConsolePortTemplate,
    fields='__all__',
    filters=ConsolePortTemplateFilter,
    pagination=True
)
class ConsolePortTemplateType(ModularComponentTemplateType):
    pass


@strawberry_django.type(
    models.ConsoleServerPort,
    exclude=['_path'],
    filters=ConsoleServerPortFilter,
    pagination=True
)
class ConsoleServerPortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
    pass


@strawberry_django.type(
    models.ConsoleServerPortTemplate,
    fields='__all__',
    filters=ConsoleServerPortTemplateFilter,
    pagination=True
)
class ConsoleServerPortTemplateType(ModularComponentTemplateType):
    pass


@strawberry_django.type(
    models.Device,
    fields='__all__',
    filters=DeviceFilter,
    pagination=True
)
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
    console_port_count: BigInt
    console_server_port_count: BigInt
    power_port_count: BigInt
    power_outlet_count: BigInt
    interface_count: BigInt
    front_port_count: BigInt
    rear_port_count: BigInt
    device_bay_count: BigInt
    module_bay_count: BigInt
    inventory_item_count: BigInt
    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
    device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]
    role: Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
    platform: Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')] | None
    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
    location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
    rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')] | None
    primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
    primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
    oob_ip: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
    cluster: Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')] | None
    virtual_chassis: Annotated["VirtualChassisType", strawberry.lazy('dcim.graphql.types')] | None

    virtual_machines: list[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]
    modules: list[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
    interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
    rearports: list[Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]]
    consoleports: list[Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')]]
    powerports: list[Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')]]
    cabletermination_set: list[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]
    consoleserverports: list[Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')]]
    poweroutlets: list[Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')]]
    frontports: list[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]
    devicebays: list[Annotated["DeviceBayType", strawberry.lazy('dcim.graphql.types')]]
    modulebays: list[Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]]
    services: list[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]
    inventoryitems: list[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]
    vdcs: list[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(prefetch_related='vc_master_for')
    def vc_master_for(self) -> Annotated["VirtualChassisType", strawberry.lazy('dcim.graphql.types')] | None:
        return self.vc_master_for if hasattr(self, 'vc_master_for') else None

    @strawberry_django.field(prefetch_related='parent_bay')
    def parent_bay(self) -> Annotated["DeviceBayType", strawberry.lazy('dcim.graphql.types')] | None:
        return self.parent_bay if hasattr(self, 'parent_bay') else None


@strawberry_django.type(
    models.DeviceBay,
    fields='__all__',
    filters=DeviceBayFilter,
    pagination=True
)
class DeviceBayType(ComponentType):
    installed_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None


@strawberry_django.type(
    models.DeviceBayTemplate,
    fields='__all__',
    filters=DeviceBayTemplateFilter,
    pagination=True
)
class DeviceBayTemplateType(ComponentTemplateType):
    pass


@strawberry_django.type(
    models.InventoryItemTemplate,
    exclude=['component_type', 'component_id', 'parent'],
    filters=InventoryItemTemplateFilter,
    pagination=True
)
class InventoryItemTemplateType(ComponentTemplateType):
    role: Annotated['InventoryItemRoleType', strawberry.lazy('dcim.graphql.types')] | None
    manufacturer: Annotated['ManufacturerType', strawberry.lazy('dcim.graphql.types')]

    @strawberry_django.field(prefetch_related='parent', only=['parent_id'])
    def parent(self) -> Annotated['InventoryItemTemplateType', strawberry.lazy('dcim.graphql.types')] | None:
        return self.parent

    child_items: list[Annotated['InventoryItemTemplateType', strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(
        prefetch_related=build_gfk_prefetch(
            'component',
            [
                models.ConsolePortTemplate,
                models.ConsoleServerPortTemplate,
                models.FrontPortTemplate,
                models.InterfaceTemplate,
                models.PowerOutletTemplate,
                models.PowerPortTemplate,
                models.RearPortTemplate,
            ],
        ),
        only=['component_type', 'component_id'],
    )
    def component(self) -> Annotated[
        Annotated['ConsolePortTemplateType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['ConsoleServerPortTemplateType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['FrontPortTemplateType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['InterfaceTemplateType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerOutletTemplateType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerPortTemplateType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['RearPortTemplateType', strawberry.lazy('dcim.graphql.types')],
        strawberry.union('InventoryItemTemplateComponentType'),
    ] | None:
        return self.component


@strawberry_django.type(
    models.DeviceRole,
    fields='__all__',
    filters=DeviceRoleFilter,
    pagination=True
)
class DeviceRoleType(NestedGroupObjectType):
    parent: Annotated['DeviceRoleType', strawberry.lazy('dcim.graphql.types')] | None
    children: list[Annotated['DeviceRoleType', strawberry.lazy('dcim.graphql.types')]]
    color: str
    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None

    virtual_machines: list[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]
    devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.DeviceType,
    fields='__all__',
    filters=DeviceTypeFilter,
    pagination=True
)
class DeviceTypeType(PrimaryObjectType):
    console_port_template_count: BigInt
    console_server_port_template_count: BigInt
    power_port_template_count: BigInt
    power_outlet_template_count: BigInt
    interface_template_count: BigInt
    front_port_template_count: BigInt
    rear_port_template_count: BigInt
    device_bay_template_count: BigInt
    module_bay_template_count: BigInt
    inventory_item_template_count: BigInt
    device_count: BigInt
    front_image: strawberry_django.fields.types.DjangoImageType | None
    rear_image: strawberry_django.fields.types.DjangoImageType | None
    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
    default_platform: Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')] | None

    frontporttemplates: list[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    modulebaytemplates: list[Annotated["ModuleBayTemplateType", strawberry.lazy('dcim.graphql.types')]]
    instances: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
    poweroutlettemplates: list[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]
    powerporttemplates: list[Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    inventoryitemtemplates: list[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]
    rearporttemplates: list[Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    consoleserverporttemplates: list[Annotated["ConsoleServerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    interfacetemplates: list[Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')]]
    devicebaytemplates: list[Annotated["DeviceBayTemplateType", strawberry.lazy('dcim.graphql.types')]]
    consoleporttemplates: list[Annotated["ConsolePortTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.FrontPort,
    fields='__all__',
    filters=FrontPortFilter,
    pagination=True
)
class FrontPortType(ModularComponentType, CabledObjectMixin):
    color: str

    mappings: list[Annotated["PortMappingType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.FrontPortTemplate,
    fields='__all__',
    filters=FrontPortTemplateFilter,
    pagination=True
)
class FrontPortTemplateType(ModularComponentTemplateType):
    color: str

    mappings: list[Annotated["PortMappingTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.MACAddress,
    exclude=['assigned_object_type', 'assigned_object_id'],
    filters=MACAddressFilter,
    pagination=True
)
class MACAddressType(PrimaryObjectType):
    mac_address: str

    @strawberry_django.field(
        prefetch_related=build_gfk_prefetch(
            'assigned_object',
            [
                models.Interface,
                VMInterface,
            ],
        ),
        only=['assigned_object_type', 'assigned_object_id'],
    )
    def assigned_object(self) -> Annotated[
        Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['VMInterfaceType', strawberry.lazy('virtualization.graphql.types')],
        strawberry.union('MACAddressAssignmentType'),
    ] | None:
        return self.assigned_object


@strawberry_django.type(
    models.Interface,
    exclude=['_path'],
    filters=InterfaceFilter,
    pagination=True
)
class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
    _name: str
    speed: BigInt | None
    wwn: str | None
    parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
    bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
    lag: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
    wireless_link: Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')] | None
    untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
    vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
    primary_mac_address: Annotated["MACAddressType", strawberry.lazy('dcim.graphql.types')] | None
    qinq_svlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
    vlan_translation_policy: Annotated["VLANTranslationPolicyType", strawberry.lazy('ipam.graphql.types')] | None
    l2vpn_termination: Annotated["L2VPNTerminationType", strawberry.lazy('vpn.graphql.types')] | None

    vdcs: list[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]
    tagged_vlans: list[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
    bridge_interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
    wireless_lans: list[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]
    member_interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
    child_interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
    mac_addresses: list[Annotated["MACAddressType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.InterfaceTemplate,
    fields='__all__',
    filters=InterfaceTemplateFilter,
    pagination=True
)
class InterfaceTemplateType(ModularComponentTemplateType):
    _name: str
    bridge: Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')] | None

    bridge_interfaces: list[Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.InventoryItem,
    exclude=['component_type', 'component_id', 'parent'],
    filters=InventoryItemFilter,
    pagination=True
)
class InventoryItemType(ComponentType):
    role: Annotated['InventoryItemRoleType', strawberry.lazy('dcim.graphql.types')] | None
    manufacturer: Annotated['ManufacturerType', strawberry.lazy('dcim.graphql.types')] | None

    child_items: list[Annotated['InventoryItemType', strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(prefetch_related='parent', only=['parent_id'])
    def parent(self) -> Annotated['InventoryItemType', strawberry.lazy('dcim.graphql.types')] | None:
        return self.parent

    @strawberry_django.field(
        prefetch_related=build_gfk_prefetch(
            'component',
            [
                models.ConsolePort,
                models.ConsoleServerPort,
                models.FrontPort,
                models.Interface,
                models.PowerOutlet,
                models.PowerPort,
                models.RearPort,
            ],
        ),
        only=['component_type', 'component_id'],
    )
    def component(self) -> Annotated[
        Annotated['ConsolePortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['ConsoleServerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['FrontPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['InterfaceType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerOutletType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['PowerPortType', strawberry.lazy('dcim.graphql.types')]
        | Annotated['RearPortType', strawberry.lazy('dcim.graphql.types')],
        strawberry.union('InventoryItemComponentType'),
    ] | None:
        return self.component


@strawberry_django.type(
    models.InventoryItemRole,
    fields='__all__',
    filters=InventoryItemRoleFilter,
    pagination=True
)
class InventoryItemRoleType(OrganizationalObjectType):
    color: str

    inventory_items: list[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]
    inventory_item_templates: list[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.Location,
    # fields='__all__',
    exclude=['parent'],  # bug - temp
    filters=LocationFilter,
    pagination=True
)
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NestedGroupObjectType):
    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
    parent: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None

    powerpanel_set: list[Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]]
    cabletermination_set: list[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]
    racks: list[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]
    devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
    children: list[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
        ),
    )
    def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
        return self.cluster_set.all()

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'circuit_terminations', info.context.request.user, 'view',
            queryset=CircuitTermination.objects.all()
        ),
    )
    def circuit_terminations(self) -> list[
        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
    ]:
        return self.circuit_terminations.all()


@strawberry_django.type(
    models.Manufacturer,
    fields='__all__',
    filters=ManufacturerFilter,
    pagination=True
)
class ManufacturerType(OrganizationalObjectType, ContactsMixin):

    platforms: list[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]
    device_types: list[Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]]
    inventory_item_templates: list[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]
    inventory_items: list[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]
    module_types: list[Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.Module,
    fields='__all__',
    filters=ModuleFilter,
    pagination=True
)
class ModuleType(PrimaryObjectType):
    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
    module_bay: Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]
    module_type: Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')]

    interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
    powerports: list[Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')]]
    consoleserverports: list[Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')]]
    consoleports: list[Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')]]
    poweroutlets: list[Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')]]
    rearports: list[Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]]
    frontports: list[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.ModuleBay,
    # fields='__all__',
    exclude=['parent'],
    filters=ModuleBayFilter,
    pagination=True
)
class ModuleBayType(ModularComponentType):

    installed_module: Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')] | None
    children: list[Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(prefetch_related='parent', only=['parent_id'])
    def parent(self) -> Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')] | None:
        return self.parent


@strawberry_django.type(
    models.ModuleBayTemplate,
    fields='__all__',
    filters=ModuleBayTemplateFilter,
    pagination=True
)
class ModuleBayTemplateType(ModularComponentTemplateType):
    pass


@strawberry_django.type(
    models.ModuleTypeProfile,
    fields='__all__',
    filters=ModuleTypeProfileFilter,
    pagination=True
)
class ModuleTypeProfileType(PrimaryObjectType):
    module_types: list[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.ModuleType,
    fields='__all__',
    filters=ModuleTypeFilter,
    pagination=True
)
class ModuleTypeType(PrimaryObjectType):
    module_count: BigInt
    console_port_template_count: BigInt
    console_server_port_template_count: BigInt
    power_port_template_count: BigInt
    power_outlet_template_count: BigInt
    interface_template_count: BigInt
    front_port_template_count: BigInt
    rear_port_template_count: BigInt
    module_bay_template_count: BigInt
    profile: Annotated["ModuleTypeProfileType", strawberry.lazy('dcim.graphql.types')] | None
    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]

    frontporttemplates: list[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    consoleserverporttemplates: list[Annotated["ConsoleServerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    interfacetemplates: list[Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')]]
    powerporttemplates: list[Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    poweroutlettemplates: list[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]
    rearporttemplates: list[Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
    instances: list[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
    consoleporttemplates: list[Annotated["ConsolePortTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.Platform,
    fields='__all__',
    filters=PlatformFilter,
    pagination=True
)
class PlatformType(NestedGroupObjectType):
    parent: Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')] | None
    children: list[Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')]]
    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')] | None
    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None

    virtual_machines: list[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]
    devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.PortMapping,
    fields='__all__',
    filters=PortMappingFilter,
    pagination=True
)
class PortMappingType(ModularComponentTemplateType):
    front_port: Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]
    rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]


@strawberry_django.type(
    models.PortTemplateMapping,
    fields='__all__',
    filters=PortTemplateMappingFilter,
    pagination=True
)
class PortMappingTemplateType(ModularComponentTemplateType):
    front_port: Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]
    rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]


@strawberry_django.type(
    models.PowerFeed,
    exclude=['_path'],
    filters=PowerFeedFilter,
    pagination=True
)
class PowerFeedType(CabledObjectMixin, PathEndpointMixin, PrimaryObjectType):
    power_panel: Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]
    rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')] | None
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None


@strawberry_django.type(
    models.PowerOutlet,
    exclude=['_path'],
    filters=PowerOutletFilter,
    pagination=True
)
class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
    power_port: Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')] | None
    color: str


@strawberry_django.type(
    models.PowerOutletTemplate,
    fields='__all__',
    filters=PowerOutletTemplateFilter,
    pagination=True
)
class PowerOutletTemplateType(ModularComponentTemplateType):
    power_port: Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')] | None
    color: str


@strawberry_django.type(
    models.PowerPanel,
    fields='__all__',
    filters=PowerPanelFilter,
    pagination=True
)
class PowerPanelType(ContactsMixin, PrimaryObjectType):
    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
    location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None

    powerfeeds: list[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.PowerPort,
    exclude=['_path'],
    filters=PowerPortFilter,
    pagination=True
)
class PowerPortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):

    poweroutlets: list[Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.PowerPortTemplate,
    fields='__all__',
    filters=PowerPortTemplateFilter,
    pagination=True
)
class PowerPortTemplateType(ModularComponentTemplateType):
    poweroutlet_templates: list[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.RackGroup,
    fields='__all__',
    filters=RackGroupFilter,
    pagination=True
)
class RackGroupType(OrganizationalObjectType):

    racks: list[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.RackType,
    fields='__all__',
    filters=RackTypeFilter,
    pagination=True
)
class RackTypeType(ImageAttachmentsMixin, PrimaryObjectType):
    rack_count: BigInt
    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]


@strawberry_django.type(
    models.Rack,
    fields='__all__',
    filters=RackFilter,
    pagination=True
)
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
    location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
    group: Annotated["RackGroupType", strawberry.lazy('dcim.graphql.types')] | None
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
    role: Annotated["RackRoleType", strawberry.lazy('dcim.graphql.types')] | None

    rack_type: Annotated["RackTypeType", strawberry.lazy('dcim.graphql.types')] | None
    reservations: list[Annotated["RackReservationType", strawberry.lazy('dcim.graphql.types')]]
    devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
    powerfeeds: list[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]]
    cabletermination_set: list[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.RackReservation,
    fields='__all__',
    filters=RackReservationFilter,
    pagination=True
)
class RackReservationType(PrimaryObjectType):
    units: list[int]
    rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')]
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
    user: Annotated["UserType", strawberry.lazy('users.graphql.types')]

    @classmethod
    def get_queryset(cls, queryset, info, **kwargs):
        queryset = super().get_queryset(queryset, info, **kwargs)
        # Annotate unit_count here so RackReservationFilter.unit_count can reference it. The field below
        # is resolved from `units` rather than from this annotation, which is not applied on every path
        # by which a RackReservation may be resolved.
        return queryset.annotate(
            unit_count=Func('units', function='CARDINALITY', output_field=IntegerField())
        )

    # Ensure `units` is fetched when `unit_count` is requested
    @strawberry_django.field(only=['units'])
    def unit_count(self) -> int:
        return len(self.units)


@strawberry_django.type(
    models.RackRole,
    fields='__all__',
    filters=RackRoleFilter,
    pagination=True
)
class RackRoleType(OrganizationalObjectType):
    color: str

    racks: list[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.RearPort,
    fields='__all__',
    filters=RearPortFilter,
    pagination=True
)
class RearPortType(ModularComponentType, CabledObjectMixin):
    color: str

    mappings: list[Annotated["PortMappingType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.RearPortTemplate,
    fields='__all__',
    filters=RearPortTemplateFilter,
    pagination=True
)
class RearPortTemplateType(ModularComponentTemplateType):
    color: str

    mappings: list[Annotated["PortMappingTemplateType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.Region,
    exclude=['parent'],
    filters=RegionFilter,
    pagination=True
)
class RegionType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):

    sites: list[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
    children: list[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(prefetch_related='parent', only=['parent_id'])
    def parent(self) -> Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None:
        return self.parent

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
        ),
    )
    def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
        return self.cluster_set.all()

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'circuit_terminations', info.context.request.user, 'view',
            queryset=CircuitTermination.objects.all()
        ),
    )
    def circuit_terminations(self) -> list[
        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
    ]:
        return self.circuit_terminations.all()


@strawberry_django.type(
    models.Site,
    fields='__all__',
    filters=SiteFilter,
    pagination=True
)
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
    time_zone: str | None
    region: Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None
    group: Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None

    prefixes: list[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]
    virtual_machines: list[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]
    racks: list[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]
    cabletermination_set: list[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]
    powerpanel_set: list[Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]]
    devices: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
    locations: list[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]
    asns: list[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]
    vlans: list[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
        ),
    )
    def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
        return self.cluster_set.all()

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'circuit_terminations', info.context.request.user, 'view',
            queryset=CircuitTermination.objects.all()
        ),
    )
    def circuit_terminations(self) -> list[
        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
    ]:
        return self.circuit_terminations.all()


@strawberry_django.type(
    models.SiteGroup,
    exclude=['parent'],  # bug - temp
    filters=SiteGroupFilter,
    pagination=True
)
class SiteGroupType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):

    sites: list[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
    children: list[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]

    @strawberry_django.field(prefetch_related='parent', only=['parent_id'])
    def parent(self) -> Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None:
        return self.parent

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'cluster_set', info.context.request.user, 'view', queryset=Cluster.objects.all()
        ),
    )
    def clusters(self) -> list[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
        return self.cluster_set.all()

    @strawberry_django.field(
        prefetch_related=lambda info: RestrictedPrefetch(
            'circuit_terminations', info.context.request.user, 'view',
            queryset=CircuitTermination.objects.all()
        ),
    )
    def circuit_terminations(self) -> list[
        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
    ]:
        return self.circuit_terminations.all()


@strawberry_django.type(
    models.VirtualChassis,
    fields='__all__',
    filters=VirtualChassisFilter,
    pagination=True
)
class VirtualChassisType(PrimaryObjectType):
    member_count: BigInt
    master: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None

    members: list[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]


@strawberry_django.type(
    models.VirtualDeviceContext,
    fields='__all__',
    filters=VirtualDeviceContextFilter,
    pagination=True
)
class VirtualDeviceContextType(PrimaryObjectType):
    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
    primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
    primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None

    interfaces: list[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
