'use strict';

const assert = require('node:assert');
const {DnsApi} = require('../../models/dns_provider/common');

/**
 * DNS Provider Contract Test Helper
 *
 * This module provides a contract test suite that validates a DNS provider
 * implementation meets all requirements. Use this when adding new DNS providers
 * to ensure they implement the required interface correctly.
 *
 * Required static properties:
 * - _keyMap: Object defining required API credentials/config
 * - displayName: String for UI display
 * - displayIconHtml: SVG markup for provider icon
 * - displayIconUni: Unicode icon fallback
 *
 * Required instance methods:
 * - listDomains(): Returns array of {domain, zoneId}
 * - getRecords(domain, options): Returns array of DNS records
 * - createRecord(domain, options): Creates a record, returns created record
 * - deleteRecords(domain, options): Deletes matching records
 *
 * Required behavior:
 * - Must extend DnsApi base class
 * - Must throw errors.unauthorized() on auth failures
 * - Must implement __apiKeyMap for key translation
 * - Must validate record types via __typeCheck
 */

/**
 * Validates a DNS provider class meets the contract
 *
 * @param {Class} ProviderClass - The DNS provider class to validate
 * @param {Object} mockCredentials - Mock credentials for testing
 * @returns {void}
 * @throws {AssertionError} If provider doesn't meet contract
 */
function validateDnsProviderContract(ProviderClass, mockCredentials) {

	// Test 1: Must extend DnsApi
	assert.ok(
		ProviderClass.prototype instanceof DnsApi,
		`${ProviderClass.name} must extend DnsApi base class`
	);

	// Test 2: Must have static _keyMap
	assert.ok(
		ProviderClass._keyMap,
		`${ProviderClass.name} must define static _keyMap`
	);

	assert.strictEqual(
		typeof ProviderClass._keyMap,
		'object',
		`${ProviderClass.name}._keyMap must be an object`
	);

	// Test 3: Must have display properties
	assert.ok(
		ProviderClass.displayName,
		`${ProviderClass.name} must define static displayName`
	);

	assert.ok(
		ProviderClass.displayIconHtml,
		`${ProviderClass.name} must define static displayIconHtml`
	);

	assert.ok(
		ProviderClass.displayIconUni,
		`${ProviderClass.name} must define static displayIconUni`
	);

	// Test 4: Can be instantiated
	let instance;
	assert.doesNotThrow(
		() => {
			instance = new ProviderClass(mockCredentials);
		},
		`${ProviderClass.name} must be instantiable with mock credentials`
	);

	// Test 5: Must have required methods
	const requiredMethods = [
		'listDomains',
		'getRecords',
		'createRecord',
		'deleteRecords'
	];

	for(let method of requiredMethods) {
		assert.strictEqual(
			typeof instance[method],
			'function',
			`${ProviderClass.name} must implement ${method}() method`
		);
	}

	// Test 6: Must have __apiKeyMap for key translation
	assert.ok(
		instance.hasOwnProperty('__apiKeyMap') || instance.constructor.prototype.hasOwnProperty('__apiKeyMap'),
		`${ProviderClass.name} must define __apiKeyMap property`
	);

	// Test 7: Must have __typeCheck method (inherited or overridden)
	assert.strictEqual(
		typeof instance.__typeCheck,
		'function',
		`${ProviderClass.name} must have __typeCheck method`
	);

	// Test 8: Must have error methods (inherited from DnsApi)
	assert.ok(
		instance.errors,
		`${ProviderClass.name} must have errors object`
	);

	assert.strictEqual(
		typeof instance.errors.unauthorized,
		'function',
		`${ProviderClass.name} must have errors.unauthorized method`
	);

	assert.strictEqual(
		typeof instance.errors.invalidInput,
		'function',
		`${ProviderClass.name} must have errors.invalidInput method`
	);

	assert.strictEqual(
		typeof instance.errors.other,
		'function',
		`${ProviderClass.name} must have errors.other method`
	);

	// Test 9: info() method should return expected structure
	const info = ProviderClass.info();
	assert.ok(info.displayName, 'info() must include displayName');
	assert.ok(info.displayIconHtml, 'info() must include displayIconHtml');
	assert.ok(info.displayIconUni, 'info() must include displayIconUni');
	assert.ok(info.fields, 'info() must include fields');

	// Test 10: toJSON() should work
	const json = instance.toJSON();
	assert.ok(json.displayName, 'toJSON() must include displayName');

	return instance;
}

/**
 * Validates method signatures for a DNS provider instance
 *
 * @param {Object} instance - Instance of DNS provider
 * @param {Object} mockDomain - Mock domain object with {domain, zoneId}
 * @returns {void}
 */
function validateMethodSignatures(instance, mockDomain = {domain: 'example.com', zoneId: 'mock-zone'}) {

	const className = instance.constructor.name;

	// These tests just verify the methods accept the expected parameters
	// and return promises (actual API calls would require real credentials)
	// We catch and suppress errors since these calls will fail with mock credentials

	// listDomains() should return a promise
	const listDomainsResult = instance.listDomains();
	assert.ok(
		listDomainsResult instanceof Promise,
		`${className}.listDomains() must return a Promise`
	);
	listDomainsResult.catch(() => {}); // Suppress unhandled rejection

	// getRecords(domain, options) should return a promise
	const getRecordsResult = instance.getRecords(mockDomain, {type: 'A'});
	assert.ok(
		getRecordsResult instanceof Promise,
		`${className}.getRecords() must return a Promise`
	);
	getRecordsResult.catch(() => {}); // Suppress unhandled rejection

	// createRecord(domain, options) should return a promise
	const createRecordResult = instance.createRecord(mockDomain, {
		type: 'TXT',
		name: 'test',
		data: 'test-value'
	});
	assert.ok(
		createRecordResult instanceof Promise,
		`${className}.createRecord() must return a Promise`
	);
	createRecordResult.catch(() => {}); // Suppress unhandled rejection

	// deleteRecords(domain, options) should return a promise
	const deleteRecordsResult = instance.deleteRecords(mockDomain, {
		type: 'TXT',
		name: 'test'
	});
	assert.ok(
		deleteRecordsResult instanceof Promise,
		`${className}.deleteRecords() must return a Promise`
	);
	deleteRecordsResult.catch(() => {}); // Suppress unhandled rejection
}

/**
 * Validates __parseOptions and __parseRes behavior
 *
 * @param {Object} instance - Instance of DNS provider
 * @returns {void}
 */
function validateKeyMapping(instance) {
	const className = instance.constructor.name;

	// Test __parseOptions normalizes keys
	if(Object.keys(instance.__apiKeyMap).length > 0) {
		const testOptions = {type: 'A'};

		// Add a class key that should be mapped to API key
		const [apiKey, clsKey] = Object.entries(instance.__apiKeyMap)[0];
		testOptions[clsKey] = 'test-value';

		const parsed = instance.__parseOptions(testOptions);

		assert.ok(
			parsed.hasOwnProperty(apiKey),
			`${className}.__parseOptions() should map '${clsKey}' to '${apiKey}'`
		);

		assert.strictEqual(
			parsed[clsKey],
			undefined,
			`${className}.__parseOptions() should remove class key '${clsKey}' after mapping`
		);
	}

	// Test __parseRes normalizes response keys
	const testResponse = [];
	const [apiKey, clsKey] = Object.entries(instance.__apiKeyMap)[0] || ['content', 'data'];
	testResponse.push({[apiKey]: 'test-value', name: 'test.example.com'});

	const parsedRes = instance.__parseRes(testResponse);

	if(Object.keys(instance.__apiKeyMap).length > 0) {
		assert.ok(
			parsedRes[0].hasOwnProperty(clsKey),
			`${className}.__parseRes() should map '${apiKey}' to '${clsKey}'`
		);
	}
}

/**
 * Validates type checking behavior
 *
 * @param {Object} instance - Instance of DNS provider
 * @returns {void}
 */
function validateTypeChecking(instance) {
	const className = instance.constructor.name;

	const validTypes = ['A', 'MX', 'CNAME', 'ALIAS', 'TXT', 'NS', 'AAAA', 'SRV', 'TLSA', 'CAA'];

	// Valid types should not throw
	for(let type of validTypes) {
		assert.doesNotThrow(
			() => instance.__typeCheck(type),
			`${className}.__typeCheck() should accept valid type '${type}'`
		);
	}

	// Invalid type should throw
	assert.throws(
		() => instance.__typeCheck('INVALID'),
		/Invalid.*type/i,
		`${className}.__typeCheck() should reject invalid types`
	);
}

module.exports = {
	validateDnsProviderContract,
	validateMethodSignatures,
	validateKeyMapping,
	validateTypeChecking
};
