# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0.  If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.

from datetime import timedelta

import dns.rcode
import dns.rdataclass
import dns.rdatatype
import pytest

import isctest

NSEC3_MARK = pytest.mark.extra_artifacts(
    [
        "*.axfr",
        "*.created",
        "dig.out.*",
        "rndc.reload.*",
        "rndc.signing.*",
        "update.out.*",
        "verify.out.*",
        "ns*/dsset-**",
        "ns*/K*",
        "ns*/settime.out.*",
        "ns*/*.db",
        "ns*/*.jbk",
        "ns*/*.jnl",
        "ns*/*.signed",
        "ns*/keygen.out.*",
        "ns3/named-*.conf",
        "ans*/ans.run",
    ]
)

default_config = {
    "dnskey-ttl": timedelta(hours=1),
    "ds-ttl": timedelta(days=1),
    "max-zone-ttl": timedelta(days=1),
    "parent-propagation-delay": timedelta(hours=1),
    "publish-safety": timedelta(hours=1),
    "retire-safety": timedelta(hours=1),
    "signatures-refresh": timedelta(days=5),
    "signatures-validity": timedelta(days=14),
    "zone-propagation-delay": timedelta(minutes=5),
}


def check_auth_nsec(response):
    rrs = []
    for rrset in response.authority:
        if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC, dns.rdatatype.NONE):
            rrs.append(rrset)
        assert not rrset.match(
            dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE
        )
    assert len(rrs) != 0, "no NSEC records found in authority section"


def check_auth_nsec3(response, iterations=0, optout=0, salt="-"):
    match = f"IN NSEC3 1 {optout} {iterations} {salt}"
    rrs = []

    for rrset in response.authority:
        if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE):
            assert match in rrset.to_text()
            rrs.append(rrset)
        assert not rrset.match(
            dns.rdataclass.IN, dns.rdatatype.NSEC, dns.rdatatype.NONE
        )

    assert len(rrs) != 0, "no NSEC3 records found in authority section"


def check_nsec3param(response, match, saltlen):
    rrs = []
    salt = "-"

    for rrset in response.answer:
        if rrset.match(dns.rdataclass.IN, dns.rdatatype.NSEC3PARAM, dns.rdatatype.NONE):
            assert match in rrset.to_text()
            if saltlen == 0:
                assert f"{match} -" in rrset.to_text()
            else:
                assert not f"{match} -" in rrset.to_text()
                salt = rrset.to_text().split()[7]

            rrs.append(rrset)
        else:
            assert rrset.match(
                dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC3PARAM
            )

    assert len(rrs) != 0

    return salt


def check_nsec3_case(server, params, nsec3=True):
    # Get test parameters.
    zone = params["zone"]
    fqdn = f"{zone}."
    policy = params["policy"]
    keydir = server.identifier
    config = default_config
    ttl = int(config["dnskey-ttl"].total_seconds())
    expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=params["key-properties"])

    # Test case.
    isctest.log.info(f"check nsec case zone {zone} policy {policy}")

    # Key files.
    keys = isctest.kasp.keydir_to_keylist(zone, keydir)
    if "external-keys" in params:
        expected2 = isctest.kasp.policy_to_properties(ttl, keys=params["external-keys"])
        for ek in expected2:
            ek.private = False
            ek.legacy = True
        expected = expected + expected2
        assert "external-keydir" in params
        extkeys = isctest.kasp.keydir_to_keylist(zone, params["external-keydir"])
        keys = keys + extkeys

    isctest.kasp.check_keys(zone, keys, expected)
    isctest.kasp.check_dnssec_verify(server, zone)
    isctest.kasp.check_apex(server, zone, keys, [])

    query = isctest.query.create(fqdn, dns.rdatatype.NSEC3PARAM)
    nsec3param_response = isctest.query.tcp(query, server.ip)
    assert nsec3param_response.rcode() == dns.rcode.NOERROR

    query = isctest.query.create(f"nosuchname.{fqdn}", dns.rdatatype.A)
    response = isctest.query.tcp(query, server.ip)
    assert response.rcode() == dns.rcode.NXDOMAIN

    if nsec3:
        # NSEC3
        minimum = params.get("soa-minimum", 3600)
        iterations = 0
        optout = 0
        saltlen = 0
        if "nsec3param" in params:
            optout = params["nsec3param"].get("optout", 0)
            saltlen = params["nsec3param"].get("salt-length", 0)

        match = f"{fqdn} {minimum} IN NSEC3PARAM 1 0 {iterations}"

        salt = check_nsec3param(nsec3param_response, match, saltlen)

        check_auth_nsec3(response, iterations, optout, salt)
    else:
        # NSEC
        assert len(nsec3param_response.answer) == 0
        check_auth_nsec(nsec3param_response)

        check_auth_nsec(response)
