#!/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 dns.query
import dns.rcode
import dns.zone
import pytest

import isctest

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(f"{ns2.identifier}/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)
