import assert from 'node:assert';
import BoxError from './boxerror.js';
import dashboard from './dashboard.js';
import database from './database.js';
import mailPasswords from './mailpasswords.js';
import hat from './hat.js';
import safe from 'safetydance';

const ID_WEBADMIN = 'cid-webadmin';
const ID_DEVELOPMENT = 'cid-development';
const ID_CLI = 'cid-cli';
const ID_SDK = 'cid-sdk';


const OIDC_CLIENTS_TABLE_NAME = 'oidcClients';
const OIDC_CLIENTS_FIELDS = [ 'id', 'secret', 'name', 'appId', 'loginRedirectUri', 'tokenSignatureAlgorithm' ];
const DEFAULT_TOKEN_SIGNATURE_ALGORITHM='RS256';

function validateId(type) {
    assert.strictEqual(typeof type, 'string');

    const types = [ ID_WEBADMIN, ID_SDK, ID_DEVELOPMENT, ID_CLI ];
    if (types.indexOf(type) === -1) return new BoxError(BoxError.BAD_FIELD, `type must be one of ${types.join(',')}`);

    return null;
}

function postProcess(result) {
    assert.strictEqual(typeof result, 'object');

    result.tokenSignatureAlgorithm = result.tokenSignatureAlgorithm || DEFAULT_TOKEN_SIGNATURE_ALGORITHM;

    return result;
}

async function add(data) {
    assert.strictEqual(typeof data.loginRedirectUri, 'string');
    assert.strictEqual(typeof data.name, 'string');
    assert.strictEqual(typeof data.appId, 'string');
    assert(data.tokenSignatureAlgorithm === 'RS256' || data.tokenSignatureAlgorithm === 'EdDSA');

    const id = data.id || 'cid-' + hat(128); // oidc addon provides the id for apps as app.id
    const secret = hat(256);

    const query = `INSERT INTO ${OIDC_CLIENTS_TABLE_NAME} (id, secret, name, appId, loginRedirectUri, tokenSignatureAlgorithm) VALUES (?, ?, ?, ?, ?, ?)`;
    const args = [ id, secret, data.name, data.appId, data.loginRedirectUri, data.tokenSignatureAlgorithm ];

    const [error] = await safe(database.query(query, args));
    if (error && error.sqlCode === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'client already exists');
    if (error) throw error;

    return { id, secret };
}

async function get(id) {
    assert.strictEqual(typeof id, 'string');

    if (id === ID_WEBADMIN) {
        const { fqdn:dashboardFqdn } = await dashboard.getLocation();
        return {
            id: ID_WEBADMIN,
            secret: 'notused',
            application_type: 'web',
            token_endpoint_auth_method: 'none',
            response_types: ['code'],
            grant_types: ['authorization_code'],
            loginRedirectUri: `https://${dashboardFqdn}/authcallback.html`
        };
    } else if (id === ID_DEVELOPMENT) {
        return {
            id: ID_DEVELOPMENT,
            secret: 'notused',
            application_type: 'native', // have to use native here to support plaintext http on localhost
            token_endpoint_auth_method: 'none',
            response_types: ['code'],
            grant_types: ['authorization_code'],
            loginRedirectUri: 'http://localhost:4000/authcallback.html'
        };
    } else if (id === ID_CLI) {
        return {
            id: ID_CLI,
            secret: 'notused',
            application_type: 'native',
            token_endpoint_auth_method: 'none',
            response_types: ['code'],
            grant_types: ['authorization_code', 'urn:ietf:params:oauth:grant-type:device_code'],
            loginRedirectUri: 'http://localhost:1312/callback'
        };
    }

    const result = await database.query(`SELECT ${OIDC_CLIENTS_FIELDS} FROM ${OIDC_CLIENTS_TABLE_NAME} WHERE id = ?`, [ id ]);
    if (result.length === 0) return null;

    return postProcess(result[0]);
}

async function update(id, data) {
    assert.strictEqual(typeof id, 'string');
    assert.strictEqual(typeof data.loginRedirectUri, 'string');
    assert.strictEqual(typeof data.name, 'string');
    assert.strictEqual(typeof data.appId, 'string');
    assert(data.tokenSignatureAlgorithm === 'RS256' || data.tokenSignatureAlgorithm === 'EdDSA');

    const result = await database.query(`UPDATE ${OIDC_CLIENTS_TABLE_NAME} SET name=?, appId=?, loginRedirectUri=?, tokenSignatureAlgorithm=? WHERE id = ?`, [ data.name, data.appId, data.loginRedirectUri, data.tokenSignatureAlgorithm, id]);
    if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'client not found');
}

async function del(id) {
    assert.strictEqual(typeof id, 'string');

    // also cleanup potentially issued oidc mailclient passwords
    await mailPasswords.delByClientId(id);

    const result = await database.query(`DELETE FROM ${OIDC_CLIENTS_TABLE_NAME} WHERE id = ?`, [ id ]);
    if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'client not found');
}

async function list() {
    const results = await database.query(`SELECT * FROM ${OIDC_CLIENTS_TABLE_NAME} ORDER BY name ASC`, []);

    results.forEach(postProcess);

    return results;
}

export default {
    add,
    get,
    del,
    update,
    list,

    validateId,

    // token client ids. we categorize them so we can have different restrictions based on the client
    ID_WEBADMIN,
    ID_DEVELOPMENT,
    ID_CLI,
    ID_SDK,
};
