const crypto = require('crypto');
const bcrypt = require('bcrypt');
const { ApiToken } = require('../../models');

const TOKEN_PREFIX_LENGTH = 12;

const serializeApiToken = (tokenInstance) => {
    if (!tokenInstance) return null;
    const tokenJson = tokenInstance.toJSON();
    return {
        id: tokenJson.id,
        name: tokenJson.name,
        token_prefix: tokenJson.token_prefix,
        created_at: tokenJson.created_at,
        updated_at: tokenJson.updated_at,
        last_used_at: tokenJson.last_used_at,
        expires_at: tokenJson.expires_at,
        revoked_at: tokenJson.revoked_at,
    };
};

const generateRawToken = () => `tt_${crypto.randomBytes(32).toString('hex')}`;

async function createApiToken({ userId, name, expiresAt, abilities = null }) {
    const rawToken = generateRawToken();
    const tokenHash = await bcrypt.hash(rawToken, 12);
    const tokenPrefix = rawToken.slice(0, TOKEN_PREFIX_LENGTH);

    const tokenRecord = await ApiToken.create({
        user_id: userId,
        name: name || 'Personal Access Token',
        token_hash: tokenHash,
        token_prefix: tokenPrefix,
        abilities,
        expires_at: expiresAt || null,
    });

    return { rawToken, tokenRecord };
}

async function findValidTokenByValue(tokenValue) {
    if (!tokenValue) return null;
    const prefix = tokenValue.slice(0, TOKEN_PREFIX_LENGTH);
    const possibleTokens = await ApiToken.findAll({
        where: { token_prefix: prefix },
        order: [['created_at', 'DESC']],
    });

    for (const token of possibleTokens) {
        if (token.revoked_at) continue;
        if (token.expires_at && token.expires_at < new Date()) continue;
        const match = await bcrypt.compare(tokenValue, token.token_hash);
        if (match) {
            return token;
        }
    }

    return null;
}

async function revokeApiToken(tokenId, userId) {
    const token = await ApiToken.findOne({
        where: { id: tokenId, user_id: userId },
    });

    if (!token) {
        return null;
    }

    if (!token.revoked_at) {
        token.revoked_at = new Date();
        await token.save();
    }

    return token;
}

async function deleteApiToken(tokenId, userId) {
    const token = await ApiToken.findOne({
        where: { id: tokenId, user_id: userId },
    });

    if (!token) {
        return null;
    }

    await token.destroy();
    return true;
}

module.exports = {
    createApiToken,
    revokeApiToken,
    deleteApiToken,
    findValidTokenByValue,
    serializeApiToken,
    TOKEN_PREFIX_LENGTH,
};
