import assert from 'node:assert';
import AuditSource from '../auditsource.js';
import BoxError from '../boxerror.js';
import dns from '../dns.js';
import domains from '../domains.js';
import { HttpError } from '@cloudron/connect-lastmile';
import { HttpSuccess } from '@cloudron/connect-lastmile';
import safe from 'safetydance';


async function add(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
    if (typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider must be a string'));

    if (!req.body.config || typeof req.body.config !== 'object') return next(new HttpError(400, 'config must be an object'));

    if ('zoneName' in req.body && typeof req.body.zoneName !== 'string') return next(new HttpError(400, 'zoneName must be a string'));
    if ('fallbackCertificate' in req.body && typeof req.body.fallbackCertificate !== 'object') return next(new HttpError(400, 'fallbackCertificate must be a object with cert and key strings'));
    if (req.body.fallbackCertificate) {
        const fallbackCertificate = req.body.fallbackCertificate;
        if (!fallbackCertificate.cert || typeof fallbackCertificate.cert !== 'string') return next(new HttpError(400, 'fallbackCertificate.cert must be a string'));
        if (!fallbackCertificate.key || typeof fallbackCertificate.key !== 'string') return next(new HttpError(400, 'fallbackCertificate.key must be a string'));
    }

    if ('tlsConfig' in req.body) {
        const tlsConfig = req.body.tlsConfig;
        if (!tlsConfig || typeof tlsConfig !== 'object') return next(new HttpError(400, 'tlsConfig must be a object with a provider string property'));
        if (!tlsConfig.provider || typeof tlsConfig.provider !== 'string') return next(new HttpError(400, 'tlsConfig.provider must be a string'));
        if ('wildcard' in tlsConfig && typeof tlsConfig.wildcard !== 'boolean') return next(new HttpError(400, 'tlsConfig.wildcard must be a boolean'));
    }

    // some DNS providers like DigitalOcean take a really long time to verify credentials (https://github.com/expressjs/timeout/issues/26)
    req.clearTimeout();

    const data = {
        zoneName: req.body.zoneName || '',
        provider: req.body.provider,
        config: req.body.config,
        fallbackCertificate: req.body.fallbackCertificate || null,
        tlsConfig: req.body.tlsConfig || { provider: 'letsencrypt-prod' }
    };

    const [error] = await safe(domains.add(req.body.domain, data, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(201, {}));
}

async function get(req, res, next) {
    assert.strictEqual(typeof req.params.domain, 'string');

    const [error, result] = await safe(domains.get(req.params.domain));
    if (error) return next(BoxError.toHttpError(error));
    if (!result) return next(new HttpError(404, 'Domain not found'));

    next(new HttpSuccess(200, domains.removePrivateFields(result)));
}

async function list(req, res, next) {
    const [error, results] = await safe(domains.list());
    if (error) return next(new HttpError(500, error));

    next(new HttpSuccess(200, { domains: results.map(domains.removeRestrictedFields) }));
}

async function setConfig(req, res, next) {
    assert.strictEqual(typeof req.params.domain, 'string');
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider must be an object'));

    if (!req.body.config || typeof req.body.config !== 'object') return next(new HttpError(400, 'config must be an object'));

    if ('zoneName' in req.body && typeof req.body.zoneName !== 'string') return next(new HttpError(400, 'zoneName must be a string'));
    if ('fallbackCertificate' in req.body && typeof req.body.fallbackCertificate !== 'object') return next(new HttpError(400, 'fallbackCertificate must be a object with cert and key strings'));
    if (req.body.fallbackCertificate) {
        const fallbackCertificate = req.body.fallbackCertificate;
        if (!fallbackCertificate.cert || typeof fallbackCertificate.cert !== 'string') return next(new HttpError(400, 'fallbackCertificate.cert must be a string'));
        if (!fallbackCertificate.key || typeof fallbackCertificate.key !== 'string') return next(new HttpError(400, 'fallbackCertificate.key must be a string'));
    }

    if ('tlsConfig' in req.body) {
        const tlsConfig = req.body.tlsConfig;
        if (!tlsConfig || typeof tlsConfig !== 'object') return next(new HttpError(400, 'tlsConfig must be a object with a provider string property'));
        if (!tlsConfig.provider || typeof tlsConfig.provider !== 'string') return next(new HttpError(400, 'tlsConfig.provider must be a string'));
        if ('wildcard' in tlsConfig && typeof tlsConfig.wildcard !== 'boolean') return next(new HttpError(400, 'tlsConfig.wildcard must be a boolean'));
    }

    // some DNS providers like DigitalOcean take a really long time to verify credentials (https://github.com/expressjs/timeout/issues/26)
    req.clearTimeout();

    const data = {
        zoneName: req.body.zoneName || '',
        provider: req.body.provider,
        config: req.body.config,
        fallbackCertificate: req.body.fallbackCertificate || null,
        tlsConfig: req.body.tlsConfig || { provider: 'letsencrypt-prod' }
    };

    const [error] = await safe(domains.setConfig(req.params.domain, data, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(204, {}));
}

async function setWellKnown(req, res, next) {
    assert.strictEqual(typeof req.params.domain, 'string');
    assert.strictEqual(typeof req.body, 'object');

    if (typeof req.body.wellKnown !== 'object') return next(new HttpError(400, 'wellKnown must be an object'));
    if (req.body.wellKnown) {
        if (Object.keys(req.body.wellKnown).some(k => typeof req.body.wellKnown[k] !== 'string')) return next(new HttpError(400, 'wellKnown is a map of strings'));
    }

    const [error] = await safe(domains.setWellKnown(req.params.domain, req.body.wellKnown || null, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(204, {}));
}

async function del(req, res, next) {
    assert.strictEqual(typeof req.params.domain, 'string');

    const [error] = await safe(domains.del(req.params.domain, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(204));
}

async function checkDnsRecords(req, res, next) {
    assert.strictEqual(typeof req.params.domain, 'string');

    if (typeof req.query.subdomain !=='string') return next(new HttpError(400, 'subdomain is required'));

    let [error, result] = await safe(domains.get(req.params.domain));
    if (error) return next(BoxError.toHttpError(error));
    if (!result) return next(new HttpError(404, 'Domain not found'));

    // some DNS providers like DigitalOcean take a really long time to verify credentials (https://github.com/expressjs/timeout/issues/26)
    req.clearTimeout();

    [error, result] = await safe(dns.checkDnsRecords(req.query.subdomain, req.params.domain));
    if (error && error.reason === BoxError.ACCESS_DENIED) return next(new HttpSuccess(200, { error: { reason: error.reason, message: error.message }}));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, { needsOverwrite: result.needsOverwrite }));
}

async function syncDnsRecords(req, res, next) {
    assert.strictEqual(typeof req.body, 'object');

    if ('domain' in req.body && typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
    if ('type' in req.body && typeof req.body.type !== 'string') return next(new HttpError(400, 'type must be a string'));

    const [error, taskId] = await safe(dns.startSyncDnsRecords(req.body));
    if (error && error.reason === BoxError.ACCESS_DENIED) return next(new HttpSuccess(200, { error: { reason: error.reason, message: error.message }}));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(201, { taskId }));
}

export default {
    add,
    get,
    list,
    setConfig,
    setWellKnown,
    del,

    checkDnsRecords,
    syncDnsRecords,
};
