# app_ipam - views.py
# (c) cavaliba.com


from urllib.parse import urlencode

from django.conf import settings
from django.db.models import Q
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.translation import gettext as _

from app_data.aaa import start_view
from app_data.data import Instance
from app_data.models import DataEAV, DataInstance
from app_data.paginator import paginator_for_count
from app_data.related import get_related
from app_data.schema import Schema
from app_ipam.common import (
    IpamIP,
    IpamSubnet,
    IpamVLAN,
    classify_query,
    compute_subnet_occupancy,
    find_child_subnets,
    get_subnet_index,
    subnet_size_class,
)


def _start(request, view):
    """Shared start_view() boilerplate for every IPAM view (same app/perm as today)."""
    return start_view(
        request,
        app="ipam",
        view=view,
        noauth="app_sirene:index",
        perm="p_ipam_access",
        noauthz="app_sirene:index",
    )


#  -----------------------------------------------------------------------------------------
def private(request):
    """IPAM landing page: search box + "list of ..." buttons. Classifies the search box
    query and redirects to the matching dedicated view (ip_view/subnet_view/vlan_list)."""

    context = _start(request, "private")
    if context["redirect"]:
        return redirect(context["redirect"])

    context["count_ip"] = Schema.count_instances("ipam_ip")
    context["count_subnet"] = Schema.count_instances("ipam_subnet")
    context["count_vlan"] = Schema.count_instances("ipam_vlan")

    query = request.GET.get("query", "").strip()
    if not query:
        return render(request, "app_ipam/private.html", context)

    kind, value = classify_query(query)

    if kind == "ip":
        return redirect(f"{reverse('app_ipam:ip_view')}?{urlencode({'ip': value})}")
    if kind == "subnet":
        return redirect(f"{reverse('app_ipam:subnet_view')}?{urlencode({'subnet': value})}")
    if kind == "text":
        return redirect(f"{reverse('app_ipam:vlan_list')}?{urlencode({'query': value})}")

    # ipv6_unsupported or empty: stay on the landing page with a message, no redirect loop
    context["query"] = query
    if kind == "ipv6_unsupported":
        context["error"] = _("IPv6 is not supported - this is an IPv4-only view.")
    return render(request, "app_ipam/private.html", context)


#  -----------------------------------------------------------------------------------------
def ip_view(request):
    """Single IP view: curated ipam_ip record (if any) + EAV-discovered objects at this exact
    address, its smallest containing subnet, and that subnet's VLAN(s)."""

    context = _start(request, "ip_view")
    if context["redirect"]:
        return redirect(context["redirect"])

    query = request.GET.get("ip", "").strip()
    context["query"] = query

    kind, value = classify_query(query)
    if kind == "ipv6_unsupported":
        context["error"] = _("IPv6 is not supported - this is an IPv4-only view.")
        return render(request, "app_ipam/ip_view.html", context)
    if kind != "ip":
        if query:
            context["error"] = _("Not a valid IPv4 address.")
        return render(request, "app_ipam/ip_view.html", context)

    ipobj = IpamIP(value, set_subnet=True)
    context["ip"] = ipobj
    context["related"] = ipobj.get_related_objects()

    vlans = []
    if ipobj.subnet:
        related = get_related(
            classname="ipam_subnet", keyname=ipobj.subnet.subnet, aaa=context["aaa"]
        )
        vlans = related.get("ipam_vlan", {}).get("instances", [])
    context["vlans"] = vlans

    ipam_ip_instance = Instance.from_keyname(classname="ipam_ip", keyname=value)
    if ipam_ip_instance and ipam_ip_instance.is_bound:
        context["ipam_ip_instance"] = ipam_ip_instance
        try:
            context["ipam_ip_description"] = ipam_ip_instance.fields[
                "description"
            ].get_first_value()
        except Exception:
            context["ipam_ip_description"] = ""
    else:
        ipam_ip_schema = Schema.from_name("ipam_ip")
        if ipam_ip_schema and ipam_ip_schema.has_create_permission(context["aaa"]):
            context["can_create_ipam_ip"] = True

    return render(request, "app_ipam/ip_view.html", context)


#  -----------------------------------------------------------------------------------------
def ip_list(request):
    """Paginated, free-text-searched list of curated ipam_ip instances only (keyname/
    displayname/description) - not an EAV-wide scan of every ipv4 field in the app (that
    would be unbounded; see ip_view's "Objects at this IP" for the EAV-discovered side)."""

    context = _start(request, "ip_list")
    if context["redirect"]:
        return redirect(context["redirect"])

    query = request.GET.get("query", "").strip()
    context["query"] = query

    qs = DataInstance.objects.filter(classname="ipam_ip")
    if query:
        desc_keynames = DataEAV.objects.filter(
            classname="ipam_ip", fieldname="description", value__icontains=query
        ).values_list("keyname", flat=True)
        qs = qs.filter(
            Q(keyname__icontains=query)
            | Q(displayname__icontains=query)
            | Q(keyname__in=list(desc_keynames))
        )
    qs = qs.order_by("keyname")

    paginator = paginator_for_count(request=request, count=qs.count(), session_key="ipam_ip_list")
    context["rows"] = []
    if not paginator:
        return render(request, "app_ipam/ip_list.html", context)

    context["paginator"] = paginator

    page_rows = list(qs[paginator.offset : paginator.limit])
    desc_by_keyname = dict(
        DataEAV.objects.filter(
            classname="ipam_ip",
            fieldname="description",
            keyname__in=[row.keyname for row in page_rows],
        ).values_list("keyname", "value")
    )
    for row in page_rows:
        row.description = desc_by_keyname.get(row.keyname, "")
    context["rows"] = page_rows

    return render(request, "app_ipam/ip_list.html", context)


#  -----------------------------------------------------------------------------------------
def subnet_view(request):
    """Single Subnet view: name/description/first-last IP/masks/size, occupancy (gated by
    CAVALIBA_IPAM_OCCUPANCY_MINPREFIX), paginated IP list, parent/child subnets, VLAN(s)."""

    context = _start(request, "subnet_view")
    if context["redirect"]:
        return redirect(context["redirect"])

    raw_query = request.GET.get("subnet", "").strip()
    context["query"] = raw_query

    kind, value = classify_query(raw_query)
    if kind == "ipv6_unsupported":
        context["error"] = _("IPv6 is not supported - this is an IPv4-only view.")
        return render(request, "app_ipam/subnet_view.html", context)
    if kind != "subnet":
        if raw_query:
            context["error"] = _("Not a valid IPv4 subnet.")
        return render(request, "app_ipam/subnet_view.html", context)

    subnet = IpamSubnet(value, set_subnet=True)
    if not subnet.subnet:
        context["error"] = _("Invalid subnet.")
        return render(request, "app_ipam/subnet_view.html", context)

    context["subnet"] = subnet
    context["aligned_note"] = value if value != raw_query else None

    child_networks = [ref.network for ref in subnet.child_subnet]
    occupancy = compute_subnet_occupancy(subnet.subnet, subnet.subobj, child_networks)
    context["occupancy"] = occupancy
    context["minprefix"] = settings.CAVALIBA_IPAM_OCCUPANCY_MINPREFIX

    context["ip_list"] = []
    if occupancy["computed"]:
        paginator = paginator_for_count(
            request=request,
            count=len(occupancy["ip_list"]),
            session_key="ipam_subnet_iplist",
        )
        if paginator:
            context["paginator"] = paginator
            context["ip_list"] = occupancy["ip_list"][paginator.offset : paginator.limit]

    related = get_related(classname="ipam_subnet", keyname=subnet.subnet, aaa=context["aaa"])
    context["vlans"] = related.get("ipam_vlan", {}).get("instances", [])

    if subnet.instance:
        context["subnet_fields"] = subnet.instance.get_dict_for_ui_detail(
            skip_external=True, skip_injected=True
        )
    else:
        ipam_subnet_schema = Schema.from_name("ipam_subnet")
        if ipam_subnet_schema and ipam_subnet_schema.has_create_permission(context["aaa"]):
            context["can_create_ipam_subnet"] = True

    return render(request, "app_ipam/subnet_view.html", context)


#  -----------------------------------------------------------------------------------------
def subnet_list(request):
    """Paginated list of ipam_subnet instances matching a free-text query (CIDR prefix or
    description substring), reusing the cached in-memory subnet index - no per-row Instance
    lookup for the listing itself, only for the (paginated-page-sized) occupancy column."""

    context = _start(request, "subnet_list")
    if context["redirect"]:
        return redirect(context["redirect"])

    query = request.GET.get("query", "").strip()
    context["query"] = query

    index = get_subnet_index()
    if query:
        query_lower = query.lower()
        filtered = [
            ref
            for ref in index
            if ref.subnet.startswith(query) or query_lower in ref.description.lower()
        ]
    else:
        filtered = index

    filtered = sorted(filtered, key=lambda ref: (ref.network.network_address, ref.prefixlen))

    paginator = paginator_for_count(
        request=request, count=len(filtered), session_key="ipam_subnet_list"
    )
    context["rows"] = []
    if not paginator:
        return render(request, "app_ipam/subnet_list.html", context)

    context["paginator"] = paginator
    context["minprefix"] = settings.CAVALIBA_IPAM_OCCUPANCY_MINPREFIX

    rows = []
    for ref in filtered[paginator.offset : paginator.limit]:
        child_networks = [c.network for c in find_child_subnets(ref.network, index)]
        occupancy = compute_subnet_occupancy(ref.subnet, ref.network, child_networks)
        size_class = subnet_size_class(ref.prefixlen)
        rows.append({"ref": ref, "occupancy": occupancy, "size_class": size_class})
    context["rows"] = rows

    return render(request, "app_ipam/subnet_list.html", context)


#  -----------------------------------------------------------------------------------------
def vlan_view(request):
    """Single VLAN view: all ipam_vlan fields plus its linked subnet(s)."""

    context = _start(request, "vlan_view")
    if context["redirect"]:
        return redirect(context["redirect"])

    keyname = request.GET.get("vlan", "").strip()
    context["query"] = keyname

    vlan = IpamVLAN(keyname)
    context["vlan"] = vlan
    if vlan.instance:
        context["vlan_fields"] = vlan.instance.get_dict_for_ui_detail(
            skip_external=True, skip_injected=True
        )
    elif keyname:
        context["error"] = _("VLAN not found.")

    return render(request, "app_ipam/vlan_view.html", context)


#  -----------------------------------------------------------------------------------------
def vlan_list(request):
    """Paginated, free-text-searched list of ipam_vlan instances (keyname/displayname/
    description)."""

    context = _start(request, "vlan_list")
    if context["redirect"]:
        return redirect(context["redirect"])

    query = request.GET.get("query", "").strip()
    context["query"] = query

    qs = DataInstance.objects.filter(classname="ipam_vlan")
    if query:
        desc_keynames = DataEAV.objects.filter(
            classname="ipam_vlan", fieldname="description", value__icontains=query
        ).values_list("keyname", flat=True)
        qs = qs.filter(
            Q(keyname__icontains=query)
            | Q(displayname__icontains=query)
            | Q(keyname__in=list(desc_keynames))
        )
    qs = qs.order_by("keyname")

    paginator = paginator_for_count(request=request, count=qs.count(), session_key="ipam_vlan_list")
    context["rows"] = []
    if not paginator:
        return render(request, "app_ipam/vlan_list.html", context)

    context["paginator"] = paginator
    context["rows"] = [IpamVLAN(row.keyname) for row in qs[paginator.offset : paginator.limit]]

    return render(request, "app_ipam/vlan_list.html", context)


#  -----------------------------------------------------------------------------------------
def calculator(request):

    context = _start(request, "calculator")
    if context["redirect"]:
        return redirect(context["redirect"])
    # aaa = context["aaa"]

    return render(request, "app_ipam/calculator.html", context)
