# app_data - views_ipam.py
# (c) cavaliba.com

"""
IPAM views moved here from app_ipam/views.py (step 1 of deprecating
app_ipam - see app_data/ipam.py's own module docstring): the search/menu
landing page (private). subnet_view/subnet_list/vlan_view/vlan_list were
not moved - removed outright, since ipam_subnet/ipam_vlan now have full
instance_detail/instance_list pages of their own (hierarchy/
technical-info/IP-list hooks - see app_data/hook.py). ipam_ip likewise has
no dedicated view here: see views.ipam_ip_jump() for the "click this IP"
redirect. The old static-table calculator() (IPv4 subnet-size reference)
was removed outright - superseded by subnet_calculator() below.

Routed at app_data:ipam_private (/data/private/ipam/... - app_ipam/urls.py
no longer exists, the old /ipam/private/... paths are gone).
app_ipam/views.py no longer exists - nothing else referenced it. Its
template moved too:
app_data/templates/app_data/ipam/private.html (_paginator.html didn't
move - it was only ever included by the now-removed subnet_list/
vlan_list/ip_list templates, so it was deleted as dead code instead).

subnet_calculator / bandwidth_calculator (app_data:ipam_subnet_calculator /
app_data:ipam_bandwidth_calculator) are newer, unrelated additions: small
interactive tools ported from the app_home UI-prototypes pool, sharing the
cv-ipam-tool-* look (see cavaliba.css). All computation runs client-side
in JS - each view just renders a static page shell.

subnet_ip_list_download() is the "Download full list (CSV)" link on an
ipam_subnet/ipam_vlan instance_detail page (see app_data/hook.py and
templates/app_data/ipam/_ip_list_table.html, which only lists the first
CAVALIBA_IPAM_IP_LIST_MAX IPs inline). It lives here rather than in
views.py because it's IPAM-specific, but gates on p_data_access like
instance_detail - not on p_ipam_access like the views above - since the
link sits on that page and the file holds nothing its reader can't
already see there.

Method inventory:

Module functions:
    private, subnet_calculator, bandwidth_calculator,
    subnet_ip_list_download
"""

from datetime import timedelta
from urllib.parse import urlencode

from django.conf import settings
from django.contrib import messages
from django.http import FileResponse, Http404
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext as _

from app_data.aaa import start_view
from app_data.data import Instance
from app_data.filestore import datafile_discard, datafile_finalize, datafile_reserve
from app_data.ipam import classify_query, ip_list_filename, subnet_ip_list_csv
from app_data.schema import Schema
from app_home.log import ERROR, INFO, WARNING, log


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,
        perm="p_ipam_access",
    )


def private(request):
    """IPAM landing page: search box + "list of ..." buttons. Classifies the
    search box query and redirects to the matching generic app_data view -
    ipam_subnet/ipam_ip's own instance_detail if an exact keyname match
    exists (ipam_subnet's keyname is the CIDR itself, ipam_ip's own jump
    view handles its hexip keyname), else that classname's instance_list
    prefiltered by the query."""

    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_data/ipam/private.html", context)

    kind, value = classify_query(query)

    if kind == "ip":
        return redirect(f"{reverse('app_data:ipam_ip_jump')}?{urlencode({'ip': value})}")

    if kind == "subnet":
        subnet_instance = Instance.from_keyname(classname="ipam_subnet", keyname=value)
        if subnet_instance:
            return redirect("app_data:instance_detail", id=subnet_instance.id)
        return redirect(
            f"{reverse('app_data:instance_list', kwargs={'classname': 'ipam_subnet'})}"
            f"?{urlencode({'query': value})}"
        )

    if kind == "text":
        return redirect(
            f"{reverse('app_data:instance_list', kwargs={'classname': 'ipam_vlan'})}"
            f"?{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_data/ipam/private.html", context)


def subnet_calculator(request):
    """Interactive IPv4 subnet calculator - static shell, all computation
    (netmask/broadcast/host range/binary breakdown/sub-subnet split) runs
    client-side in JS, so no query params are needed here."""

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

    return render(request, "app_data/ipam/subnet_calculator.html", context)


def bandwidth_calculator(request):
    """Interactive data-transfer duration calculator - static shell, all
    computation (bandwidth x size -> humanized duration) runs client-side
    in JS, so no query params are needed here."""

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

    return render(request, "app_data/ipam/bandwidth_calculator.html", context)


def subnet_ip_list_download(request, id=None):
    """Download every ipam_ip entry within an ipam_subnet as CSV - the full
    list its instance_detail page only shows the first
    CAVALIBA_IPAM_IP_LIST_MAX rows of.

    Built on demand: the file is written, finalized and streamed within
    this request, so no file is produced by ordinary page views, and the
    link carries the subnet's id rather than a fileid - it can't go stale
    when the DataFile expires (short TTL,
    CAVALIBA_IPAM_IP_LIST_FILE_TTL_HOURS, reaped by filestore_cleanup).

    Not served by views.file_display: that resolves the owning instance
    through the DataEAV "file" entry, and this file belongs to no instance,
    so it would always be refused. Gated here on read permission of the
    ipam_subnet itself instead. On any failure the DataFile row and blob
    are both discarded (same contract as tasks.task_export), so a
    truncated CSV is never downloadable and never left as a nameless
    orphan."""

    context = start_view(
        request,
        app="ipam",
        view="subnet_ip_list_download",
        perm="p_data_access",
        noauthz="app_home:private",
    )
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    instance = Instance.from_id(id, expand=False)
    if not instance or instance.classname != "ipam_subnet":
        raise Http404

    if not instance.has_read_permission(aaa=aaa):
        messages.add_message(request, messages.ERROR, _("Not allowed"))
        log(WARNING, aaa=aaa, app="ipam", view="subnet_ip_list_download", action="get", status="KO",
            data=f"Not allowed on ipam_subnet:{instance.keyname}")  # fmt: skip
        return redirect("app_home:private")

    not_after = timezone.now() + timedelta(hours=settings.CAVALIBA_IPAM_IP_LIST_FILE_TTL_HOURS)
    df = None

    try:
        df = datafile_reserve(filename=ip_list_filename(instance.keyname), not_after=not_after)
        with open(df.filepath, "w", encoding="utf-8", newline="") as out:
            count = subnet_ip_list_csv(instance.keyname, out)
        datafile_finalize(df)
    except Exception as exc:
        if df:
            datafile_discard(df)
        messages.add_message(request, messages.ERROR, _("IP list not available"))
        log(ERROR, aaa=aaa, app="ipam", view="subnet_ip_list_download", action="get", status="FAILED",
            data=f"{instance.keyname}: {exc}")  # fmt: skip
        return redirect("app_data:instance_detail", id=instance.id)

    log(INFO, aaa=aaa, app="ipam", view="subnet_ip_list_download", action="get", status="OK",
        data=f"{instance.keyname}: {count} ip")  # fmt: skip

    return FileResponse(open(df.filepath, "rb"), as_attachment=True, filename=df.filename)
