#!/usr/bin/env python3

'''Ctl conf and zone commands test.'''

import os

from dnstest.libknot import libknot
from dnstest.module import ModStats
from dnstest.test import Test
from dnstest.utils import *

t = Test()

knot = t.server("knot")

# Enable a global module to check the modules reuse doesn't crash the server.
knot.add_module(None, ModStats())

ctl = libknot.control.KnotCtl()

ZONE_NAME = "testzone."

t.start()

sockname = knot.ctl_sock_rnd(name_only=True)
ctl.connect(os.path.join(knot.dir, sockname))

# Check conf-abort and conf-commit without conf transaction open.
ctl.send_block(cmd="conf-abort")
resp = ctl.receive_block()

try:
    ctl.send_block(cmd="conf-commit")
    resp = ctl.receive_block()
except libknot.control.KnotCtlError as exc:
    isset(exc.message == "no active transaction", "abort error code")
else:
    set_err("UNEXPECTED RETURN")

# Check repeated conf-commit after fixed semcheck issues.
ctl.send_block(cmd="conf-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-set", section="remote", item="id", data="test")
resp = ctl.receive_block()

try:
    ctl.send_block(cmd="conf-commit")
    resp = ctl.receive_block()
except libknot.control.KnotCtlError as exc:
    isset(exc.message == "no remote address defined", "commit error code")
else:
    set_err("UNEXPECTED RETURN")

ctl.send_block(cmd="conf-set", section="remote", identifier="test", item="address", data="::1")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-commit")
resp = ctl.receive_block()

# Add new zone.
ctl.send_block(cmd="conf-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-set", section="zone", item="domain", data=ZONE_NAME)
resp = ctl.receive_block()

ctl.send_block(cmd="conf-set", section="zone", item="file", identifier=ZONE_NAME,
               data=os.path.join(knot.dir, ZONE_NAME + "zone"))
resp = ctl.receive_block()

ctl.send_block(cmd="conf-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-read", section="zone")
resp = ctl.receive_block()

isset(ZONE_NAME in resp['zone'], "zone configured")
isset("file" in resp['zone'][ZONE_NAME], "zone.file configured")

# Fill the zone content.
ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="@", ttl="3600", rtype="SOA",
               data="a. b. 1 2 3 4 5")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="@", ttl="3600", rtype="A",
               data="192.168.0.1")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-get", zone=ZONE_NAME, owner="@")
resp = ctl.receive_block()

isset("A" in resp[ZONE_NAME][ZONE_NAME], "txn A presence")
isset("192.168.0.1" in resp[ZONE_NAME][ZONE_NAME]["A"]["data"], "txn A rdata")

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="utqvuhu2blk3dhmrr5t1hd9vteohqt0a." + ZONE_NAME,
               ttl="3600", rtype="NSEC3", data="1 0 10 - dks9i43rb5utfau23saq45qmv6stlthu A RRSIG")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-get", zone=ZONE_NAME, owner="utqvuhu2blk3dhmrr5t1hd9vteohqt0a." + ZONE_NAME)
resp = ctl.receive_block()

isset("NSEC3" in resp[ZONE_NAME]["utqvuhu2blk3dhmrr5t1hd9vteohqt0a." + ZONE_NAME], "txn NSEC3 presence")
isset("1 0 10 - dks9i43rb5utfau23saq45qmv6stlthu A RRSIG" in \
      resp[ZONE_NAME]["utqvuhu2blk3dhmrr5t1hd9vteohqt0a." + ZONE_NAME]["NSEC3"]["data"], "txn NSEC3 rdata")

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", zone=ZONE_NAME)
resp = ctl.receive_block()

isset(ZONE_NAME in resp, "zone content")
isset("SOA" in resp[ZONE_NAME][ZONE_NAME], "zone SOA presence")
isset("3600" in resp[ZONE_NAME][ZONE_NAME]["SOA"]["ttl"], "zone SOA ttl")
isset("a. b. 1 2 3 4 5" in resp[ZONE_NAME][ZONE_NAME]["SOA"]["data"], "zone SOA rdata")
isset("A" in resp[ZONE_NAME][ZONE_NAME], "zone A presence")
isset("3600" in resp[ZONE_NAME][ZONE_NAME]["A"]["ttl"], "zone A ttl")
isset("192.168.0.1" in resp[ZONE_NAME][ZONE_NAME]["A"]["data"], "zone A rdata")
isset("NSEC3" in resp[ZONE_NAME]["utqvuhu2blk3dhmrr5t1hd9vteohqt0a."+ZONE_NAME], "zone NSEC3 presence")
isset("1 0 10 - dks9i43rb5utfau23saq45qmv6stlthu A RRSIG" in resp[ZONE_NAME]["utqvuhu2blk3dhmrr5t1hd9vteohqt0a."+ZONE_NAME]["NSEC3"]["data"], "zone NSEC3 rdata")

# Remove NSEC3 node.

ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner="utqvuhu2blk3dhmrr5t1hd9vteohqt0a")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", zone=ZONE_NAME)
resp = ctl.receive_block()

if "utqvuhu2blk3dhmrr5t1hd9vteohqt0a."+ZONE_NAME in resp[ZONE_NAME]:
    set_err("zone NSEC3 removal")

ctl.send(libknot.control.KnotCtlType.END)
ctl.close()

# Check the zone.
resp = knot.dig(ZONE_NAME, "SOA")
resp.check(rcode="NOERROR")

sockname = knot.ctl_sock_rnd(name_only=True)
ctl.connect(os.path.join(knot.dir, sockname))

# Abort remove SOA.
ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner="@", rtype="SOA")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-abort")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read")
resp = ctl.receive_block()

isset(ZONE_NAME in resp, "zone content")
isset("SOA" in resp[ZONE_NAME][ZONE_NAME], "zone SOA presence")

# Commit removed A.
ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner="@", rtype="A")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read")
resp = ctl.receive_block()

isset(ZONE_NAME in resp, "zone content")
isset("A" not in resp[ZONE_NAME][ZONE_NAME], "zone A presence")

# Test removing whole rrset and whole node.

ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner=ZONE_NAME, ttl="3600", rtype="TXT",
               data="text")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="rrset", ttl="3600", rtype="A",
               data="192.168.0.2")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="rrset", ttl="3600", rtype="A",
               data="192.168.0.3")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="rrset", ttl="3600", rtype="AAAA",
               data="3::4")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="node", ttl="3600", rtype="A",
               data="192.168.0.2")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="node", ttl="3600", rtype="AAAA",
               data="1::2")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", zone=ZONE_NAME)
resp = ctl.receive_block()

isset("\"text\"" in resp[ZONE_NAME][ZONE_NAME]["TXT"]["data"], "rrset TXT presence")
isset("A" in resp[ZONE_NAME]["rrset." + ZONE_NAME], "rrset A presence")
isset("192.168.0.2" in resp[ZONE_NAME]["rrset." + ZONE_NAME]["A"]["data"], "rrset A rdata 1")
isset("192.168.0.3" in resp[ZONE_NAME]["rrset." + ZONE_NAME]["A"]["data"], "rrset A rdata 2")
isset("A" in resp[ZONE_NAME]["node." + ZONE_NAME], "node A presence")
isset("AAAA" in resp[ZONE_NAME]["node." + ZONE_NAME], "node AAAA presence")

ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner=ZONE_NAME, rtype="SOA")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner=ZONE_NAME)
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner="rrset", rtype="A")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner="node")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", zone=ZONE_NAME)
resp = ctl.receive_block()

isset("SOA" in resp[ZONE_NAME][ZONE_NAME], "rrset SOA presence in apex") # SOA must be preserved
isset("TXT" not in resp[ZONE_NAME][ZONE_NAME], "rrset TXT absence in apex")
isset("A" not in resp[ZONE_NAME]["rrset." + ZONE_NAME], "rrset A absence")
isset(("node." + ZONE_NAME) not in resp[ZONE_NAME], "node absence")

# Check for proper handling of upper letter-case in the owner name.
ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-set", zone=ZONE_NAME, owner="lETter", ttl="3600", rtype="DNAME", data="tEXt.")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", zone=ZONE_NAME, owner="letter")
resp = ctl.receive_block()
record = resp[ZONE_NAME]["letter." + ZONE_NAME]
isset(record["DNAME"], "lower-cased and inserted node lETter")
isset(record["DNAME"]["data"][0] == "text.", "lower-cased rdata tEXt.")

ctl.send_block(cmd="zone-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-unset", zone=ZONE_NAME, owner="lETter")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", zone=ZONE_NAME)
resp = ctl.receive_block()
isset("letter." + ZONE_NAME not in resp[ZONE_NAME], "lower-cased and removed node lETter")

# Purge the zone data.
ctl.send_block(cmd="zone-purge", flags="B")
resp = ctl.receive_block()

ctl.send_block(cmd="zone-read", section="test-zone")
resp = ctl.receive_block()

isset(ZONE_NAME not in resp, "zone content")

resp = knot.dig(ZONE_NAME, "SOA")
resp.check(rcode="SERVFAIL")

# Abort removed zone.
ctl.send_block(cmd="conf-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-unset", section="zone", item="domain", data=ZONE_NAME)
resp = ctl.receive_block()

ctl.send_block(cmd="conf-abort")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-read", section="zone")
resp = ctl.receive_block()

isset(ZONE_NAME in resp["zone"], "zone configured")

# Commit removed zone.
ctl.send_block(cmd="conf-begin")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-unset", section="zone", item="domain", data=ZONE_NAME)
resp = ctl.receive_block()

ctl.send_block(cmd="conf-commit")
resp = ctl.receive_block()

ctl.send_block(cmd="conf-read", section="zone")
resp = ctl.receive_block()

isset("zone" not in resp, "zone not configured")

ctl.send(libknot.control.KnotCtlType.END)
ctl.close()

resp = knot.dig(ZONE_NAME, "SOA")
resp.check(rcode="REFUSED")

t.end()
