-- unload modules which are not related to this test
-- SPDX-License-Identifier: GPL-3.0-or-later

if ta_signal_query then
        modules.unload('ta_signal_query')
end
if priming then
        modules.unload('priming')
end
if detect_time_skew then
        modules.unload('detect_time_skew')
end

-- test. domain is used by some tests, allow it
policy.add(policy.suffix(policy.PASS, {todname('test.')}))

cache.size = 2*MB
-- log_level('debug')

-- Self-checks on globals
assert(help() ~= nil)
assert(worker.id ~= nil)
-- Self-checks on facilities
assert(cache.stats() ~= nil)
assert(cache.backends() ~= nil)
assert(worker.stats() ~= nil)
assert(net.interfaces() ~= nil)
-- Self-checks on loaded stuff
assert(#modules.list() > 0)
-- Self-check timers
ev = event.recurrent(1 * sec, function () return 1 end)
event.cancel(ev)
ev = event.after(0, function () return 1 end)


-- Import fake root zone; avoid interference with configured keyfile_default.
trust_anchors.remove('.')
trust_anchors.add('. IN DS 48409 8 2 3D63A0C25BCE86621DE63636F11B35B908EFE8E9381E0E3E9DEFD89EA952C27D')

local check_answer = require('test_utils').check_answer

-- do not attempt to contact outside world, operate only on cache
net.ipv4 = false
net.ipv6 = false
-- do not listen, test is driven by config code
env.KRESD_NO_LISTEN = true


local function import_zone()
	local import_res = require('ffi').C.zi_zone_import({ zone_file = 'testroot.zone' })
	assert(import_res == 0)
	-- beware that import takes at least 100 ms
	worker.sleep(0.2)  -- zimport is delayed by 100 ms from function call
	-- sanity checks - cache must be filled in
	ok(cache.count() > 0, 'cache is not empty after import')
	check_answer('root apex is in cache',
                     '.', kres.type.NS, kres.rcode.NOERROR)
	check_answer('deep subdomain is in cache',
		     'a.b.subtree1.', kres.type.AAAA, kres.rcode.NOERROR)

end

local function test_exact_match_qtype()
	nok(cache.clear('a.b.subtree1.', true, kres.type.A)['chunk_limit'],
	     'single qname+qtype can be cleared at once')
	check_answer('exact match on qname+qtype must flush RR from cache',
		     'a.b.subtree1.', kres.type.A, kres.rcode.SERVFAIL)
	check_answer('exact match on qname+qtype must not affect other RRs on the same node',
		     'a.b.subtree1.', kres.type.AAAA, kres.rcode.NOERROR)
	check_answer('exact match on qname must not affect parent',
		     'b.subtree1.', kres.type.A, kres.rcode.NOERROR)
end

local function test_exact_match_qname()
	res = cache.clear('a.b.SubTree1.')
	is(res.count, 2, 'single qname can be cleared at once')
	check_answer('exact match on qname must flush all RRs with the same owner from cache',
		     'a.b.subtree1.', kres.type.AAAA, kres.rcode.SERVFAIL)
	check_answer('exact match on qname must flush all RRs with the same owner from cache',
		     'a.b.subtree1.', kres.type.A, kres.rcode.SERVFAIL)
	check_answer('exact match on qname must flush all RRs with the same owner from cache',
		     'a.b.subtree1.', kres.type.TXT, kres.rcode.SERVFAIL)
	-- exact match for negative proofs is not implemented yet
	--check_answer('exact match on qname must flush negative proofs for owner from cache',
	--	     'a.b.subtree1.', kres.type.NULL, kres.rcode.SERVFAIL)
	--check_answer('exact match on qname must not affect parent',
	--	     'b.subtree1.', kres.type.A, kres.rcode.NOERROR)
	-- same(cache.clear(), 0, 'full cache clear can be performed')
	--check_answer('.', kres.type.NS, false)

end

local function test_subtree()
	res = cache.clear('subtree1.')
	nok(res.chunk_limit,
	    'whole positive subtree must be flushed (does not include neg. proofs)')
	ok(res.not_apex,
	    'subtree clear below apex must be detected')
        same(res.subtree, '.', 'detected apex must be returned')
	check_answer('subtree variant must flush all RRs in subdomains from cache',
		     'b.subtree1.', kres.type.A, kres.rcode.SERVFAIL)
	check_answer('subtree variant must flush all RRs in subdomains from cache',
		     'b.subtree1.', kres.type.TXT, kres.rcode.SERVFAIL)
	check_answer('subtree variant must flush all RRs in subdomains from cache',
		     'subtree1.', kres.type.TXT, kres.rcode.SERVFAIL)
	check_answer('subtree variant must not affect parent',
		     '.', kres.type.NS, kres.rcode.NOERROR)
	-- same(cache.clear(), 0, 'full cache clear can be performed')
	--check_answer('.', kres.type.NS, false)

end

local function test_callback()
	local test_name = '20r.subtree2.'
	local test_exactname = true
	local test_rrtype = nil
	local test_chunksize = 1
	local test_prev_state = { works = true }
	local function check_callback(name, exact_name, rr_type, chunk_size, callback, prev_state, errors)
		is(errors.count, 1, 'callback received correct # of removed records')
		is(test_name, name, 'callback received subtree name')
		is(test_exactname, exact_name, 'callback received exact_name')
		is(test_rrtype, rr_type, 'callback received rr_type')
		is(test_chunksize, chunk_size, 'callback received chunk_size')
		is(check_callback, callback, 'callback received reference to itself')
		is(type(errors), 'table', 'callback received table of errors')
		same(test_prev_state, prev_state, 'callback received previous state')
		return 666
	end
	same(cache.clear(test_name, test_exactname, test_rrtype, test_chunksize, check_callback, test_prev_state),
	     666, 'first callback return value is passed to cache.clear() caller')
	local cnt_before_wait = cache.count()
	worker.sleep(0.2)
	is(cnt_before_wait, cache.count(), 'custom callback can stop clearing')
end

local function test_subtree_limit()  -- default limit = 100
	res = cache.clear('subtree2.', false, nil)
	ok(res.chunk_limit,
	   'chunk_size limit must be respected')
	is(res.count, 100,
	   'chunk_size limit must match returned count')

	-- callbacks are running in background so we can now wait
	-- and later verify that everything was removed
	-- 200 RRs, 100 was removed in first call
	-- so the rest should be removed in single invocation of callback
	-- hopefully the machine is not too slow ...
	worker.sleep(0.1)
	res = cache.clear('subtree2.', false, nil)
	is(res.count, 0,
	   'previous calls + callbacks must have removed everything')
end

local function test_apex()
	check_answer('a negative proof is still present in cache',
		     'aaaaa.b.subtree1.', kres.type.TXT, kres.rcode.NXDOMAIN)

	local prev_count = cache.count()
	ok(prev_count > 0, 'previous subtree clearing did not remove everything')
	res = cache.clear('.', false, nil, 10000)
	is(res.count, prev_count, 'clear on root removed everything including proofs')
	check_answer('exact match on qname must flush negative proofs for owner from cache',
		     'a.b.subtree1.', kres.type.NULL, kres.rcode.SERVFAIL)
end

local function test_root()
	check_answer('root apex is still in cache',
                     '.', kres.type.NS, kres.rcode.NOERROR)
	res = cache.clear('.', true)
	check_answer('root apex is in no longer cache',
                     '.', kres.type.NS, kres.rcode.SERVFAIL)
	check_answer('some other item is still in cache',
                     '16r.subtree2.', kres.type.A, kres.rcode.NOERROR)

	local prev_count = cache.count()
	res = cache.clear('.')
	is(res.count, prev_count, 'full clear reports correct number of entries')
	is(cache.count(), 0, 'clearing root clears everything')
end

local function test_complete_flush()
	local prev_count = cache.count()
	res = cache.clear()
	is(res.count, prev_count, 'full clear reports correct number of entries')
	is(cache.count(), 0, 'cache is empty after full clear')
end

local function test_cache_used(lower, upper)
	return function()
		local usage = cache.stats().usage_percent
		ok(usage >= lower and usage <= upper,
		   string.format('cache percentage usage %.1f is between <%d, %d>', usage, lower, upper))
	end
end

return {
	test_cache_used(0, 1),
	import_zone,
	test_cache_used(9, 11),
	test_exact_match_qtype,
	test_exact_match_qname,
	test_callback,
	import_zone,
	test_subtree,
	test_cache_used(9, 11),
	test_subtree_limit,
	test_cache_used(5, 8),
	test_apex,
	import_zone,
	test_root,
	import_zone,
	test_complete_flush,
	test_cache_used(0, 1),
}
