import db from '../database/models/index.js';
const { SLATracking, SLAConfiguration, SLABreach, Application } = db;
import { Op } from 'sequelize';
import { slaConfigLookupNames } from '../common/config/slaStageCatalog.js';
import { computeSlaTrackView, formatSlaDuration, type SlaBucket } from '../common/utils/slaMetrics.js';
import { SlaStatusService } from './SlaStatusService.js';
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';

export type { SlaBucket };

function moduleFromEntityType(entityType: string): string {
    const map: Record<string, string> = {
        application: 'ONBOARDING',
        termination: 'TERMINATION',
        resignation: 'RESIGNATION',
        relocation: 'RELOCATION',
        constitutional: 'CONSTITUTIONAL',
        fnf: 'FNF'
    };
    return map[entityType] || entityType.toUpperCase();
}

function linkForEntity(entityType: string, entityId: string): string {
    const base = getFrontendBaseUrl();
    switch (entityType) {
        case 'application':
            return `${base}/applications/${entityId}`;
        case 'termination':
            return `${base}/termination/${entityId}`;
        case 'resignation':
            return `${base}/resignation/${entityId}`;
        case 'relocation':
            return `${base}/relocation/${entityId}`;
        case 'constitutional':
            return `${base}/constitutional-change/${entityId}`;
        case 'fnf':
            return `${base}/fnf/${entityId}`;
        default:
            return base;
    }
}

async function resolveCaseRef(entityType: string, entityId: string, applicationId: string | null): Promise<string> {
    if (entityType === 'application') {
        const app = await Application.findByPk(entityId, { attributes: ['applicationId'] });
        return app?.applicationId || entityId;
    }
    if (entityType === 'termination') {
        const row = await db.TerminationRequest.findByPk(entityId, { attributes: ['requestId'] });
        return row?.requestId || entityId;
    }
    if (entityType === 'resignation') {
        const row = await db.Resignation.findByPk(entityId, { attributes: ['resignationId'] });
        return row?.resignationId || entityId;
    }
    if (entityType === 'relocation') {
        const row = await db.RelocationRequest.findByPk(entityId, { attributes: ['requestId'] });
        return row?.requestId || entityId;
    }
    if (entityType === 'constitutional') {
        const row = await db.ConstitutionalChange.findByPk(entityId, { attributes: ['requestId'] });
        return row?.requestId || entityId;
    }
    if (entityType === 'fnf') {
        const row = await db.FnF.findByPk(entityId, { attributes: ['settlementId'] });
        return row?.settlementId || entityId;
    }
    if (applicationId) {
        const app = await Application.findByPk(applicationId, { attributes: ['applicationId'] });
        return app?.applicationId || applicationId;
    }
    return entityId;
}

async function getSchedulerSnapshot() {
    const redisEnabled = process.env.ENABLE_REDIS === 'true';
    const snapshot: any = {
        redisEnabled,
        slaFastMode: process.env.DEBUG_SLA_FAST_MODE === 'true',
        questionnaireFastMode: process.env.DEBUG_QUESTIONNAIRE_REMINDER_FAST_MODE === 'true',
        queues: [] as any[]
    };

    if (!redisEnabled) {
        return snapshot;
    }

    try {
        const { slaQueue } = await import('../common/queues/sla.queue.js');
        const slaCounts = await slaQueue.getJobCounts('active', 'waiting', 'completed', 'failed', 'delayed');
        const slaRepeat = await slaQueue.getRepeatableJobs();
        snapshot.queues.push({
            name: 'SLA breach checker',
            key: 'slaQueue',
            counts: slaCounts,
            repeatable: slaRepeat
        });
    } catch (e) {
        snapshot.queues.push({ name: 'SLA breach checker', error: String(e) });
    }

    try {
        const { questionnaireReminderQueue } = await import('../common/queues/questionnaire-reminder.queue.js');
        const qCounts = await questionnaireReminderQueue.getJobCounts(
            'active',
            'waiting',
            'completed',
            'failed',
            'delayed'
        );
        const qRepeat = await questionnaireReminderQueue.getRepeatableJobs();
        snapshot.queues.push({
            name: 'Prospect questionnaire reminders',
            key: 'questionnaireReminderQueue',
            counts: qCounts,
            repeatable: qRepeat
        });
    } catch (e) {
        snapshot.queues.push({ name: 'Prospect questionnaire reminders', error: String(e) });
    }

    try {
        const { offboardingLwdQueue } = await import('../common/queues/offboarding-lwd.queue.js');
        const lwdCounts = await offboardingLwdQueue.getJobCounts(
            'active',
            'waiting',
            'completed',
            'failed',
            'delayed'
        );
        const lwdRepeat = await offboardingLwdQueue.getRepeatableJobs();
        snapshot.queues.push({
            name: 'Offboarding LWD → F&F',
            key: 'offboardingLwdQueue',
            counts: lwdCounts,
            repeatable: lwdRepeat
        });
    } catch (e) {
        snapshot.queues.push({ name: 'Offboarding LWD', error: String(e) });
    }

    return snapshot;
}

async function computeAnalytics(moduleFilter?: string) {
    const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
    const tracksStarted = await SLATracking.findAll({
        where: { startTime: { [Op.gte]: since } },
        attributes: ['id', 'entityType', 'stageName', 'startTime', 'endTime', 'isBreached']
    });

    const filteredTracks = moduleFilter
        ? tracksStarted.filter((t: any) => moduleFromEntityType(t.entityType || 'application') === moduleFilter)
        : tracksStarted;

    const breaches30d = await SLABreach.findAll({
        where: { breachedAt: { [Op.gte]: since } },
        include: [{ model: SLATracking, as: 'slaTracking', required: true, attributes: ['entityType', 'stageName'] }]
    });

    const filteredBreaches = moduleFilter
        ? breaches30d.filter((b: any) => {
              const t = b.slaTracking;
              return t && moduleFromEntityType(t.entityType || 'application') === moduleFilter;
          })
        : breaches30d;

    const startedCount = filteredTracks.length;
    const breachCount = filteredBreaches.length;
    const breachRatePercent =
        startedCount > 0 ? Math.round((breachCount / startedCount) * 1000) / 10 : 0;

    const completed = filteredTracks.filter((t: any) => t.endTime);
    let avgResolutionHours: number | null = null;
    if (completed.length > 0) {
        const totalMs = completed.reduce((sum: number, t: any) => {
            return sum + (new Date(t.endTime).getTime() - new Date(t.startTime).getTime());
        }, 0);
        avgResolutionHours = Math.round((totalMs / completed.length / (60 * 60 * 1000)) * 10) / 10;
    }

    const stageCounts = new Map<string, { breaches: number; activeBreached: number }>();
    for (const b of filteredBreaches) {
        const stage = (b as any).slaTracking?.stageName || 'Unknown';
        const cur = stageCounts.get(stage) || { breaches: 0, activeBreached: 0 };
        cur.breaches++;
        stageCounts.set(stage, cur);
    }

    const activeBreached = await SLATracking.findAll({
        where: { isActive: true, endTime: null, isBreached: true },
        attributes: ['stageName', 'entityType']
    });
    for (const t of activeBreached) {
        const mod = moduleFromEntityType((t as any).entityType || 'application');
        if (moduleFilter && mod !== moduleFilter) continue;
        const stage = t.stageName;
        const cur = stageCounts.get(stage) || { breaches: 0, activeBreached: 0 };
        cur.activeBreached++;
        stageCounts.set(stage, cur);
    }

    const topDelayedStages = [...stageCounts.entries()]
        .map(([stageName, counts]) => ({
            stageName,
            breachCount: counts.breaches,
            currentlyBreached: counts.activeBreached,
            score: counts.breaches + counts.activeBreached * 2
        }))
        .sort((a, b) => b.score - a.score)
        .slice(0, 8);

    const moduleBreachCounts: Record<string, number> = {};
    for (const b of filteredBreaches) {
        const mod = moduleFromEntityType((b as any).slaTracking?.entityType || 'application');
        moduleBreachCounts[mod] = (moduleBreachCounts[mod] || 0) + 1;
    }

    return {
        periodDays: 30,
        tracksStarted: startedCount,
        breachesRecorded: breachCount,
        breachRatePercent,
        avgResolutionHours,
        completedTracks: completed.length,
        topDelayedStages,
        breachesByModule: moduleBreachCounts
    };
}

export class SlaOperationsService {
    static async getDashboard(
        filters: { module?: string; breachedOnly?: boolean; mineOnly?: boolean; userRoleCode?: string | null } = {}
    ) {
        const now = Date.now();
        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 activeTracks = await SLATracking.findAll({
            where: { isActive: true, endTime: null },
            include: [{ model: Application, as: 'application', required: false, attributes: ['applicationId', 'applicantName'] }],
            order: [['startTime', 'ASC']]
        });

        const queueItems: any[] = [];
        const buckets = { healthy: 0, warning: 0, critical: 0, breached: 0 };
        const byModule: Record<string, number> = {};

        for (const track of activeTracks) {
            const entityType = (track as any).entityType || 'application';
            const entityId = (track as any).entityId || track.applicationId;
            const mod = moduleFromEntityType(entityType);

            if (filters.module && mod !== filters.module) continue;

            const config = slaConfigLookupNames(track.stageName)
                .map((name) => configByStage.get(name))
                .find(Boolean) as { ownerRole: string; tatHours: number; tatUnit: string } | undefined;
            if (!config) continue;

            const view = computeSlaTrackView(track, config, now);
            const { deadline, percentUsed, bucket, isBreached } = view;

            if (filters.mineOnly && filters.userRoleCode) {
                if (!SlaStatusService.filterQueueByOwnerRole([{ ownerRole: config.ownerRole }], filters.userRoleCode).length) {
                    continue;
                }
            }

            if (filters.breachedOnly && bucket !== 'breached') continue;

            buckets[bucket]++;
            byModule[mod] = (byModule[mod] || 0) + 1;

            const caseRef = await resolveCaseRef(entityType, entityId, track.applicationId);
            const msRemaining = deadline.getTime() - now;

            queueItems.push({
                trackingId: track.id,
                entityType,
                entityId,
                applicationId: track.applicationId,
                module: mod,
                caseRef,
                stageName: track.stageName,
                ownerRole: config.ownerRole,
                startTime: track.startTime,
                deadline,
                percentUsed,
                bucket,
                isBreached,
                remainingLabel:
                    msRemaining > 0
                        ? formatSlaDuration(msRemaining) + ' left'
                        : formatSlaDuration(-msRemaining) + ' overdue',
                link: linkForEntity(entityType, entityId)
            });
        }

        queueItems.sort((a, b) => {
            const order = { breached: 0, critical: 1, warning: 2, healthy: 3 };
            const bo = (order[a.bucket as SlaBucket] ?? 9) - (order[b.bucket as SlaBucket] ?? 9);
            if (bo !== 0) return bo;
            return b.percentUsed - a.percentUsed;
        });

        const openBreaches = await SLABreach.findAll({
            where: { status: { [Op.in]: ['Open', 'open', 'OPEN'] } },
            include: [
                {
                    model: SLATracking,
                    as: 'slaTracking',
                    required: false
                }
            ],
            order: [['breachedAt', 'DESC']],
            limit: 100
        });

        const breachRows: any[] = [];
        for (const breach of openBreaches) {
            const tracking = (breach as any).slaTracking;
            if (!tracking) continue;
            const entityType = tracking.entityType || 'application';
            const entityId = tracking.entityId || tracking.applicationId;
            const mod = moduleFromEntityType(entityType);
            if (filters.module && mod !== filters.module) continue;

            breachRows.push({
                id: breach.id,
                breachedAt: breach.breachedAt,
                status: breach.status,
                stageName: tracking.stageName,
                module: mod,
                caseRef: await resolveCaseRef(entityType, entityId, tracking.applicationId),
                link: linkForEntity(entityType, entityId)
            });
        }

        const configStats = await SLAConfiguration.findAll({
            attributes: ['isActive'],
            raw: true
        });
        const configsActive = configStats.filter((c: any) => c.isActive).length;
        const configsInactive = configStats.length - configsActive;

        const activeCount = queueItems.length;
        const breachedCount = queueItems.filter((q) => q.bucket === 'breached').length;
        const dueSoonCount = queueItems.filter((q) => q.bucket === 'critical' || q.bucket === 'warning').length;
        const onTrackCount = queueItems.filter((q) => q.bucket === 'healthy').length;

        const tracksWithoutConfig = activeTracks.filter((t: any) => {
            return !slaConfigLookupNames(t.stageName).some((name) => configByStage.has(name));
        }).length;

        const analytics = await computeAnalytics(filters.module);

        return {
            generatedAt: new Date().toISOString(),
            scheduler: await getSchedulerSnapshot(),
            analytics,
            summary: {
                activeCount,
                breachedCount,
                dueSoonCount,
                onTrackCount,
                openBreachesCount: breachRows.length,
                tracksWithoutConfig,
                configsActive,
                configsInactive,
                buckets,
                byModule
            },
            activeQueue: queueItems,
            breaches: breachRows
        };
    }

    static toCsv(dashboard: Awaited<ReturnType<typeof SlaOperationsService.getDashboard>>): string {
        const header = 'caseRef,module,stage,owner,percentUsed,bucket,remaining,deadline\n';
        const rows = dashboard.activeQueue.map((q) =>
            [
                q.caseRef,
                q.module,
                `"${String(q.stageName).replace(/"/g, '""')}"`,
                `"${String(q.ownerRole).replace(/"/g, '""')}"`,
                q.percentUsed,
                q.bucket,
                `"${q.remainingLabel}"`,
                new Date(q.deadline).toISOString()
            ].join(',')
        );
        return header + rows.join('\n');
    }
}

