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


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

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

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

async function listDomains(req, res, next) {
    const [error, results] = await safe(mail.listDomains());
    if (error) return next(BoxError.toHttpError(error));

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

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

    // can take a while to query all the DNS entries
    req.clearTimeout();

    const [error, records] = await safe(mail.getStatus(req.params.domain));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, records));
}

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

    if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled is required'));

    const [error] = await safe(mail.setMailFromValidation(req.params.domain, req.body.enabled));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

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

    if (!req.body.addresses) return next(new HttpError(400, 'addresses is required'));
    if (!Array.isArray(req.body.addresses)) return next(new HttpError(400, 'addresses must be an array of strings'));

    for (let i = 0; i < req.body.addresses.length; i++) {
        if (typeof req.body.addresses[i] !== 'string') return next(new HttpError(400, 'addresses must be an array of strings'));
    }

    const [error] = await safe(mail.setCatchAllAddress(req.params.domain, req.body.addresses));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

async function setMailRelay(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 is required'));
    if ('host' in req.body && typeof req.body.host !== 'string') return next(new HttpError(400, 'host must be a string'));
    if ('port' in req.body && typeof req.body.port !== 'number') return next(new HttpError(400, 'port must be a string'));
    if ('username' in req.body && typeof req.body.username !== 'string') return next(new HttpError(400, 'username must be a string'));
    if ('password' in req.body && typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a string'));
    if ('acceptSelfSignedCerts' in req.body && typeof req.body.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'acceptSelfSignedCerts must be a boolean'));
    if ('forceFromAddress' in req.body && typeof req.body.forceFromAddress !== 'boolean') return next(new HttpError(400, 'forceFromAddress must be a boolean'));

    const [error] = await safe(mail.setMailRelay(req.params.domain, req.body, { skipVerify: false }));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

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

    if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled is required'));

    // can take a while to restart the mail container
    req.clearTimeout();

    const [error] = await safe(mail.setMailEnabled(req.params.domain, !!req.body.enabled, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

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

    if (!req.body.to || typeof req.body.to !== 'string') return next(new HttpError(400, 'to must be a non-empty string'));

    const [error] = await safe(mail.sendTestMail(req.params.domain, req.body.to));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

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

    const page = typeof req.query.page === 'string' ? parseInt(req.query.page) : 1;
    if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a positive number'));

    const perPage = typeof req.query.per_page === 'string'? parseInt(req.query.per_page) : 25;
    if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a positive number'));

    const [error, mailboxes] = await safe(mail.listMailboxesByDomain(req.params.domain, page, perPage));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, { mailboxes }));
}

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

    const [error, stats] = await safe(mail.getStats(req.params.domain));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, stats));
}

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

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

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

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

    if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string'));
    if (typeof req.body.ownerId !== 'string') return next(new HttpError(400, 'ownerId must be a string'));
    if (typeof req.body.ownerType !== 'string') return next(new HttpError(400, 'ownerType must be a string'));
    if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));
    if (typeof req.body.enablePop3 !== 'boolean') return next(new HttpError(400, 'enablePop3 must be a boolean'));

    if (!Number.isInteger(req.body.storageQuota)) return next(new HttpError(400, 'storageQuota must be an integer'));
    if (req.body.storageQuota < 0) return next(new HttpError(400, 'storageQuota must be a postive integer or zero'));
    if (!Number.isInteger(req.body.messagesQuota)) return next(new HttpError(400, 'messagesQuota must be an integer'));
    if (req.body.messagesQuota < 0) return next(new HttpError(400, 'messagesQuota must be a positive integer or zero'));

    const [error] = await safe(mail.addMailbox(req.body.name, req.params.domain, req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

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

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

    if (typeof req.body.ownerId !== 'string') return next(new HttpError(400, 'ownerId must be a string'));
    if (typeof req.body.ownerType !== 'string') return next(new HttpError(400, 'ownerType must be a string'));
    if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));
    if (typeof req.body.enablePop3 !== 'boolean') return next(new HttpError(400, 'enablePop3 must be a boolean'));

    if (!Number.isInteger(req.body.storageQuota)) return next(new HttpError(400, 'storageQuota must be an integer'));
    if (req.body.storageQuota < 0) return next(new HttpError(400, 'storageQuota must be a postive integer or zero'));
    if (!Number.isInteger(req.body.messagesQuota)) return next(new HttpError(400, 'messagesQuota must be an integer'));
    if (req.body.messagesQuota < 0) return next(new HttpError(400, 'messagesQuota must be a positive integer or zero'));

    const [error] = await safe(mail.updateMailbox(req.params.name, req.params.domain, req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(204));
}

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

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

    const [error] = await safe(mail.delMailbox(req.params.name, req.params.domain, req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

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

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

    const [error, aliases] = await safe(mail.getAliases(req.params.name, req.params.domain));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, { aliases }));
}

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

    if (!Array.isArray(req.body.aliases)) return next(new HttpError(400, 'aliases must be an array'));

    for (const alias of req.body.aliases) {
        if (!alias || typeof alias !== 'object') return next(new HttpError(400, 'each alias must have a name and domain'));
        if (typeof alias.name !== 'string') return next(new HttpError(400, 'name must be a string'));
        if (typeof alias.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
    }

    const [error] = await safe(mail.setAliases(req.params.name, req.params.domain, req.body.aliases, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

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

    if (typeof req.body.text !== 'string') return res.status(400).send({ message: 'text must be a string' });
    if ('html' in req.body && typeof req.body.html !== 'string') return res.status(400).send({ message: 'html must be a string' });

    const [error] = await safe(mail.setBanner(req.params.domain, { text: req.body.text, html: req.body.html || null }));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(202));
}

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

    const page = typeof req.query.page === 'string' ? parseInt(req.query.page) : 1;
    if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a positive number'));

    const perPage = typeof req.query.per_page === 'string'? parseInt(req.query.per_page) : 25;
    if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a positive number'));

    const [error, lists] = await safe(mail.listMailingListsByDomain(req.params.domain, page, perPage));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(200, { lists }));
}

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

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

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

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

    if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string'));
    if (!Array.isArray(req.body.members)) return next(new HttpError(400, 'members must be a string'));
    if (req.body.members.length === 0) return next(new HttpError(400, 'list must have atleast one member'));

    for (let i = 0; i < req.body.members.length; i++) {
        if (typeof req.body.members[i] !== 'string') return next(new HttpError(400, 'member must be a string'));
    }
    if (typeof req.body.membersOnly !== 'boolean') return next(new HttpError(400, 'membersOnly must be a boolean'));
    if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));

    const [error] = await safe(mail.addMailingList(req.body.name, req.params.domain, req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

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

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

    if (!Array.isArray(req.body.members)) return next(new HttpError(400, 'members must be a string'));
    if (req.body.members.length === 0) return next(new HttpError(400, 'list must have atleast one member'));

    for (let i = 0; i < req.body.members.length; i++) {
        if (typeof req.body.members[i] !== 'string') return next(new HttpError(400, 'member must be a string'));
    }
    if (typeof req.body.membersOnly !== 'boolean') return next(new HttpError(400, 'membersOnly must be a boolean'));
    if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));

    const [error] = await safe(mail.updateMailingList(req.params.name, req.params.domain, req.body, AuditSource.fromRequest(req)));
    if (error) return next(BoxError.toHttpError(error));

    next(new HttpSuccess(204));
}

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

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

    next(new HttpSuccess(204));
}

export default {
    getDomain,
    listDomains,

    getStatus,

    setMailFromValidation,
    setCatchAllAddress,
    setMailRelay,
    setMailEnabled,
    setBanner,

    sendTestMail,

    listMailboxesByDomain,
    getMailbox,
    addMailbox,
    updateMailbox,
    delMailbox,

    getAliases,
    setAliases,

    listMailingListsByDomain,
    getMailingList,
    addMailingList,
    updateMailingList,
    delMailingList,

    getStats
};
