#!/usr/bin/python3

# 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.


import os
import re
import sys

import isctest
import pytest

import dns
import dns.exception
import dns.message
import dns.name
import dns.query
import dns.rcode
import dns.rdataclass
import dns.rdatatype

pytestmark = [
    pytest.mark.skipif(
        sys.version_info < (3, 7), reason="Python >= 3.7 required [GL #3001]"
    ),
    pytest.mark.extra_artifacts(
        [
            "*.out",
            "ns2/*.infile",
            "ns2/*.signed",
            "ns2/*.jnl",
            "ns2/*.jbk",
            "ns2/controls.conf",
            "ns2/dsset-*",
            "ns2/K*",
        ]
    ),
]


def has_nsec3param(zone, response):
    match = rf"{re.escape(zone)}\.\s+\d+\s+IN\s+NSEC3PARAM\s+1\s+0\s+0\s+-"

    for rr in response.answer:
        if re.search(match, rr.to_text()):
            return True

    return False


def do_query(server, qname, qtype, tcp=False):
    msg = isctest.query.create(qname, qtype)
    query_func = isctest.query.tcp if tcp else isctest.query.udp
    response = query_func(msg, server.ip, expected_rcode=dns.rcode.NOERROR)
    return response


def do_xfr(server, qname):
    xfr = dns.zone.Zone(origin=f"{qname}.", relativize=False)
    dns.query.inbound_xfr(
        where=server.ip, txn_manager=xfr, port=int(os.environ["PORT"])
    )
    return xfr


def verify_zone(zone, transfer):
    verify = os.getenv("VERIFY")
    assert verify is not None

    filename = f"{zone}.out"
    with open(filename, "w", encoding="utf-8") as file:
        file.write(transfer.to_text())

    # dnssec-verify command with default arguments.
    verify_cmd = [verify, "-z", "-o", zone, filename]

    verifier = isctest.run.cmd(verify_cmd)

    if verifier.rc != 0:
        isctest.log.error(f"dnssec-verify {zone}. failed")

    return verifier.rc == 0


def test_optout(ns2):
    zone = "test"
    expect_nsec3param = True

    # Wait until the provided zone is signed and then verify its DNSSEC data.
    def check_nsec3param():
        response = do_query(ns2, zone, "NSEC3PARAM")
        if expect_nsec3param:
            return has_nsec3param(zone, response)
        return not has_nsec3param(zone, response)

    # check zone is fully signed.
    isctest.run.retry_with_timeout(check_nsec3param, timeout=100)

    # check if zone if DNSSEC valid.
    transfer = do_xfr(ns2, zone)
    assert verify_zone(zone, transfer)


def test_optout_to_nsec(ns2, templates):
    zone = "small.test"
    expect_nsec3param = True

    # Wait until the provided zone is signed and then verify its DNSSEC data.
    def check_nsec3param():
        response = do_query(ns2, zone, "NSEC3PARAM")
        if expect_nsec3param:
            return has_nsec3param(zone, response)
        return not has_nsec3param(zone, response)

    # check zone is fully signed.
    isctest.run.retry_with_timeout(check_nsec3param, timeout=100)

    # check if zone if DNSSEC valid.
    transfer = do_xfr(ns2, zone)
    assert verify_zone(zone, transfer)

    # reconfigure to NSEC.
    data = {
        "reconfiged": True,
    }
    templates.render("ns2/named.conf", data)
    ns2.reconfigure()

    # wait until NSEC3PARAM is removed.
    expect_nsec3param = False
    isctest.run.retry_with_timeout(check_nsec3param, timeout=100)

    # check if zone if DNSSEC valid.
    transfer = do_xfr(ns2, zone)
    assert verify_zone(zone, transfer)
