import crypto from 'crypto';
import bcrypt from 'bcryptjs';
import { ApiToken } from '../models/ApiToken';
import { User } from '../models/User';
import logger from '../utils/logger';

export class ApiTokenService {
    private static readonly TOKEN_PREFIX = 're_';
    private static readonly TOKEN_LENGTH = 32;

    /**
     * Generate a new API token for a user
     */
    async createToken(userId: string, name: string, expiresInDays?: number): Promise<{ token: string; apiToken: ApiToken }> {
        try {
            // 1. Generate secure random token
            const randomBytes = crypto.randomBytes(ApiTokenService.TOKEN_LENGTH);
            const randomString = randomBytes.toString('hex');
            const token = `${ApiTokenService.TOKEN_PREFIX}${randomString}`;

            // 2. Hash the token
            const tokenHash = await bcrypt.hash(token, 10);

            // 3. Calculate expiry
            let expiresAt: Date | undefined;
            if (expiresInDays) {
                expiresAt = new Date();
                expiresAt.setDate(expiresAt.getDate() + expiresInDays);
            }

            // 4. Save to DB
            const apiToken = await ApiToken.create({
                userId,
                name,
                prefix: token.substring(0, 8), // Store first 8 chars (prefix + 5 chars) for lookup
                tokenHash,
                expiresAt,
                isActive: true,
            });

            logger.info(`API Token created for user ${userId}: ${name}`);

            // Return raw token ONLY here
            return { token, apiToken };
        } catch (error) {
            logger.error('Error creating API token:', error);
            throw new Error('Failed to create API token');
        }
    }

    /**
     * List all active tokens for a user
     */
    async listTokens(userId: string): Promise<ApiToken[]> {
        return await ApiToken.findAll({
            where: { userId, isActive: true },
            order: [['createdAt', 'DESC']],
            attributes: { exclude: ['tokenHash'] } // Never return hash
        });
    }

    /**
     * Revoke (delete) a token
     */
    async revokeToken(userId: string, tokenId: string): Promise<boolean> {
        const result = await ApiToken.destroy({
            where: { id: tokenId, userId }
        });
        return result > 0;
    }

    /**
     * Verify an API token and return the associated user
     */
    async verifyToken(rawToken: string): Promise<User | null> {
        try {
            if (!rawToken.startsWith(ApiTokenService.TOKEN_PREFIX)) {
                return null;
            }

            // 1. Find potential tokens by prefix (optimization)
            const prefix = rawToken.substring(0, 8);
            const tokens = await ApiToken.findAll({
                where: {
                    prefix,
                    isActive: true
                },
                include: [{
                    model: User,
                    as: 'user',
                    where: { isActive: true } // Ensure user is also active
                }]
            });

            // 2. check hash for each candidate
            for (const tokenRecord of tokens) {
                const isValid = await bcrypt.compare(rawToken, tokenRecord.tokenHash);
                if (isValid) {
                    // 3. Check expiry
                    if (tokenRecord.expiresAt && new Date() > tokenRecord.expiresAt) {
                        logger.warn(`Expired API token used: ${tokenRecord.id}`);
                        return null;
                    }

                    // 4. Update last used
                    await tokenRecord.update({ lastUsedAt: new Date() });

                    // Return the user (casted as any because include types can be tricky)
                    return (tokenRecord as any).user;
                }
            }

            return null;
        } catch (error) {
            logger.error('Error verifying API token:', error);
            return null;
        }
    }
}
