import type { Request } from 'express';
import db from '../database/models/index.js';
import type { AuthRequest } from '../types/express.types.js';

const { SystemAuditLog } = db;

export type SystemAuditPayload = {
    userId?: string | null;
    actorName?: string | null;
    actorRole?: string | null;
    module: string;
    entityType: string;
    entityId?: string | null;
    entityLabel?: string | null;
    action: string;
    description?: string | null;
    oldData?: Record<string, unknown> | null;
    newData?: Record<string, unknown> | null;
    metadata?: Record<string, unknown> | null;
    ipAddress?: string | null;
    userAgent?: string | null;
};

export type RequestContext = {
    userId: string | null;
    actorName: string | null;
    actorRole: string | null;
    ipAddress: string | null;
    userAgent: string | null;
};

/**
 * Snapshot the actor + transport context from an Express request so the
 * caller doesn't have to repeat the same property plucking every time.
 * Returns nulls for unauthenticated requests (e.g. public questionnaire).
 */
export function extractRequestContext(req: Request | AuthRequest | undefined): RequestContext {
    const r: any = req || {};
    const user = r.user || {};
    const ipAddress =
        (r.headers?.['x-forwarded-for'] as string | undefined)?.split(',')[0]?.trim() ||
        r.ip ||
        r.connection?.remoteAddress ||
        null;
    const userAgent =
        (r.get && typeof r.get === 'function' && r.get('user-agent')) ||
        r.headers?.['user-agent'] ||
        null;

    return {
        userId: user.id || null,
        actorName: user.fullName || user.firstName || null,
        actorRole: user.roleCode || user.role || null,
        ipAddress: ipAddress ? String(ipAddress).slice(0, 64) : null,
        userAgent: userAgent ? String(userAgent).slice(0, 500) : null
    };
}

/**
 * Writes a system-audit row. Never throws — system/config writes must not
 * break the workflow that triggered them. Failures are logged to stdout/err
 * so they show up in the Winston combined log.
 */
export async function safeSystemAuditLogCreate(payload: SystemAuditPayload): Promise<void> {
    try {
        await SystemAuditLog.create({
            userId: payload.userId ?? null,
            actorName: payload.actorName ?? null,
            actorRole: payload.actorRole ?? null,
            module: payload.module,
            entityType: payload.entityType,
            entityId: payload.entityId ? String(payload.entityId) : null,
            entityLabel: payload.entityLabel ?? null,
            action: payload.action,
            description: payload.description ?? null,
            oldData: payload.oldData ?? null,
            newData: payload.newData ?? null,
            metadata: payload.metadata ?? null,
            ipAddress: payload.ipAddress ?? null,
            userAgent: payload.userAgent ?? null
        } as any);
    } catch (err) {
        console.error(
            '[safeSystemAuditLogCreate] Non-fatal system-audit failure:',
            (err as Error)?.message || err
        );
    }
}

/**
 * Convenience wrapper that pulls the actor + transport context from `req`
 * and merges it into the payload. Caller still supplies module / action /
 * entity fields and any old/new data they want recorded.
 */
export async function logSystemAudit(
    req: Request | AuthRequest | undefined,
    payload: Omit<
        SystemAuditPayload,
        'userId' | 'actorName' | 'actorRole' | 'ipAddress' | 'userAgent'
    > &
        Partial<Pick<SystemAuditPayload, 'userId' | 'actorName' | 'actorRole'>>
): Promise<void> {
    const ctx = extractRequestContext(req);
    await safeSystemAuditLogCreate({
        ...ctx,
        ...payload
    });
}
