/**
 * errorClass → vendor-LAYER attribution.
 *
 * The monitor engine classifies every failure into an `errorClass` on the
 * Heartbeat row (see src/lib/monitor-strategies/classify-error.ts). This module
 * is the single, documented source of truth that maps each class to the vendor
 * layer responsible for it, so downtime can be attributed to the vendor we pay
 * for that layer.
 *
 * The map is a constant today; it's isolated here so it can become
 * operator-configurable later without touching the SLO math (see the design
 * doc's open questions).
 *
 * A `layer` is NOT the same as a Vendor `type`: a monitor's *primary* vendor
 * may be its HOST, but a TIMEOUT on that monitor is attributed to the ISP
 * layer. The breakdown is by layer, independent of the single-vendor link.
 */
import type { ErrorClass } from '@/lib/monitor-strategies';

export type VendorLayer = 'ISP' | 'HOST' | 'SSL' | 'DNS' | 'CDN' | 'APP';

/** All concrete layers, in display order. CDN has no errorClass mapping yet. */
export const VENDOR_LAYERS: readonly VendorLayer[] = ['ISP', 'HOST', 'SSL', 'DNS', 'CDN', 'APP'];

/**
 * Spec mapping. `null` = unattributed (we don't force UNKNOWN onto a vendor).
 * Every ErrorClass MUST have an entry — the smoke + a test guard completeness.
 */
export const ERROR_CLASS_TO_LAYER: Record<ErrorClass, VendorLayer | null> = {
    DNS_FAIL: 'DNS',
    TLS_INVALID: 'SSL',
    TCP_REJECT: 'HOST',
    TIMEOUT: 'ISP',
    HTTP_5XX: 'APP',
    HTTP_4XX: 'APP',
    KEYWORD_MISS: 'APP',
    UNKNOWN: null,
};

export function layerForErrorClass(
    errorClass: ErrorClass | string | null | undefined,
): VendorLayer | null {
    if (!errorClass) return null;
    if (Object.prototype.hasOwnProperty.call(ERROR_CLASS_TO_LAYER, errorClass)) {
        return ERROR_CLASS_TO_LAYER[errorClass as ErrorClass];
    }
    return null;
}

export interface ErrorClassCount {
    errorClass: ErrorClass | string | null | undefined;
    count: number;
}

export interface LayerAttribution {
    byLayer: Record<VendorLayer, number>;
    unattributed: number;
    totalDownSamples: number;
    percentByLayer: Record<VendorLayer, number>;
    percentUnattributed: number;
}

function zeroLayers(): Record<VendorLayer, number> {
    return { ISP: 0, HOST: 0, SSL: 0, DNS: 0, CDN: 0, APP: 0 };
}

function round2(n: number): number {
    return Math.round(n * 100) / 100;
}

/**
 * Aggregate per-errorClass down-sample counts into a per-layer breakdown +
 * percentages. UNKNOWN/unmapped classes accumulate into `unattributed`.
 */
export function attributeDowntimeByLayer(counts: ErrorClassCount[]): LayerAttribution {
    const byLayer = zeroLayers();
    let unattributed = 0;

    for (const { errorClass, count } of counts) {
        const layer = layerForErrorClass(errorClass);
        if (layer) byLayer[layer] += count;
        else unattributed += count;
    }

    const totalDownSamples =
        VENDOR_LAYERS.reduce((sum, l) => sum + byLayer[l], 0) + unattributed;

    const percentByLayer = zeroLayers();
    let percentUnattributed = 0;
    if (totalDownSamples > 0) {
        for (const l of VENDOR_LAYERS) {
            percentByLayer[l] = round2((byLayer[l] / totalDownSamples) * 100);
        }
        percentUnattributed = round2((unattributed / totalDownSamples) * 100);
    }

    return { byLayer, unattributed, totalDownSamples, percentByLayer, percentUnattributed };
}
