import db from '../database/models/index.js';
const { SLATracking, SLAConfiguration } = db;
import { Op } from 'sequelize';
import { slaConfigLookupNames } from '../common/config/slaStageCatalog.js';
import { computeSlaTrackView, ownerRoleMatchesUser } from '../common/utils/slaMetrics.js';

export type SlaEntityRef = { entityType: string; entityId: string };

/**
 * `sla_tracking.entityId` is a UUID column in Postgres. Callers (e.g. the
 * frontend, before a request payload has loaded) sometimes pass a human-
 * readable request code such as `CC-2026-MAY-00002`, which makes Postgres
 * throw `invalid input syntax for type uuid` and bubbles up as a 500.
 *
 * Cheaply filter those out before they hit the DB. We return `null` for
 * non-UUID refs so the caller's response shape is preserved.
 */
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
const isUuid = (value: string): boolean => UUID_RE.test(value);

export type SlaStatusSnapshot = {
    entityType: string;
    entityId: string;
    stageName: string;
    ownerRole: string;
    percentUsed: number;
    bucket: string;
    isBreached: boolean;
    isPaused?: boolean;
    remainingLabel: string;
    deadline: string;
};

export class SlaStatusService {
    static refKey(entityType: string, entityId: string) {
        return `${entityType}:${entityId}`;
    }

    static async getBatchStatus(refs: SlaEntityRef[]): Promise<Record<string, SlaStatusSnapshot | null>> {
        const result: Record<string, SlaStatusSnapshot | null> = {};
        if (!refs.length) return result;

        // Seed every requested ref as null so the response shape stays stable
        // even when we skip non-UUIDs (see UUID_RE above).
        for (const ref of refs) {
            result[this.refKey(ref.entityType, ref.entityId)] = null;
        }

        const uuidRefs = refs.filter((r) => isUuid(r.entityId));
        if (!uuidRefs.length) return result;

        const configs = await SLAConfiguration.findAll({
            where: { isActive: true },
            attributes: ['activityName', 'ownerRole', 'tatHours', 'tatUnit']
        });
        const configByStage = new Map(configs.map((c: any) => [c.activityName, c]));

        const entityIds = [...new Set(uuidRefs.map((r) => r.entityId))];
        const entityTypes = [...new Set(uuidRefs.map((r) => r.entityType))];

        const tracks = await SLATracking.findAll({
            where: {
                entityId: { [Op.in]: entityIds },
                entityType: { [Op.in]: entityTypes },
                isActive: true,
                endTime: null
            },
            order: [['startTime', 'DESC']]
        });

        const trackByRef = new Map<string, any>();
        for (const track of tracks) {
            const key = this.refKey((track as any).entityType, (track as any).entityId);
            if (!trackByRef.has(key)) trackByRef.set(key, track);
        }

        const now = Date.now();
        for (const ref of uuidRefs) {
            const key = this.refKey(ref.entityType, ref.entityId);
            const track = trackByRef.get(key);
            if (!track) {
                result[key] = null;
                continue;
            }
            const config = slaConfigLookupNames(track.stageName)
                .map((name) => configByStage.get(name))
                .find(Boolean) as { ownerRole: string; tatHours: number; tatUnit: string } | undefined;
            if (!config) {
                result[key] = null;
                continue;
            }
            const view = computeSlaTrackView(track, config, now);
            result[key] = {
                entityType: ref.entityType,
                entityId: ref.entityId,
                stageName: track.stageName,
                ownerRole: config.ownerRole,
                percentUsed: view.percentUsed,
                bucket: view.bucket,
                isBreached: view.isBreached,
                isPaused: view.isPaused,
                remainingLabel: view.remainingLabel,
                deadline: view.deadline.toISOString()
            };
        }

        return result;
    }

    static filterQueueByOwnerRole<T extends { ownerRole: string }>(items: T[], userRoleCode?: string | null): T[] {
        if (!userRoleCode) return items;
        return items.filter((item) => ownerRoleMatchesUser(item.ownerRole, userRoleCode));
    }
}
