import type {
    GrowBetterAssessment,
    GrowBetterDirection,
    GrowBetterModel,
} from '@/types/grow-better';

// Keep this formula aligned with App\Services\GrowBetter\GrowBetterScoreCalculator.

function roundTo(value: number, precision = 0): number {
    const factor = 10 ** precision;

    return Math.round(value * factor) / factor;
}

function scoreHigher(value: number, minimum: number, target: number): number {
    if (target <= minimum || value <= minimum) {
        return 0;
    }

    if (value >= target) {
        return 100;
    }

    return roundTo(((value - minimum) / (target - minimum)) * 100, 2);
}

function scoreLower(value: number, target: number, maximum: number): number {
    if (maximum <= target) {
        return 0;
    }

    if (value <= target) {
        return 100;
    }

    if (value >= maximum) {
        return 0;
    }

    return roundTo((1 - (value - target) / (maximum - target)) * 100, 2);
}

function scoreBalanced(value: number, low: number, high: number): number {
    if (high <= low) {
        return 0;
    }

    if (value >= low && value <= high) {
        return 100;
    }

    const span = (high - low) / 2;

    if (value < low) {
        return scoreHigher(value, low - span, low);
    }

    return scoreLower(value, high, high + span);
}

export function metricScore(
    value: number,
    direction: GrowBetterDirection,
    floor: number,
    target: number,
): number {
    if (direction === 'lower') {
        return scoreLower(value, target, floor);
    }

    if (direction === 'balanced') {
        return scoreBalanced(value, floor, target);
    }

    return scoreHigher(value, floor, target);
}

export function pointGain(score: number): number {
    return 20 + Math.round((70 - Math.min(score, 70)) * 0.9);
}

export function gradeFor(score: number): { grade: string; stage: string } {
    if (score >= 800) {
        return { grade: 'Thriving', stage: 'Scale Ready' };
    }

    if (score >= 700) {
        return { grade: 'Growing Well', stage: 'Growth Ready' };
    }

    if (score >= 600) {
        return { grade: 'Building Momentum', stage: 'On Track' };
    }

    if (score >= 450) {
        return { grade: 'Stabilising', stage: 'Needs Focus' };
    }

    return { grade: 'At Risk', stage: 'Act Now' };
}

export function normalizeWeights(
    weights: Record<string, number>,
): Record<string, number> {
    const total = Object.values(weights).reduce(
        (sum, weight) => sum + weight,
        0,
    );

    if (total === 0) {
        return Object.fromEntries(
            Object.entries(weights).map(([key, weight]) => [
                key,
                Math.trunc(weight),
            ]),
        );
    }

    const normalized: Record<string, number> = {};

    for (const [key, weight] of Object.entries(weights)) {
        normalized[key] = Math.round((weight / total) * 100);
    }

    const drift =
        100 -
        Object.values(normalized).reduce((sum, weight) => sum + weight, 0);

    if (drift !== 0) {
        let recipient: string | null = null;
        let largest = -1;

        for (const [key, weight] of Object.entries(normalized)) {
            if (weight > largest) {
                largest = weight;
                recipient = key;
            }
        }

        if (recipient !== null) {
            normalized[recipient] += drift;
        }
    }

    return normalized;
}

export function formatValue(
    value: number | null,
    unit: string,
    currency = 'USD',
): string | null {
    if (value === null || Number.isNaN(value)) {
        return null;
    }

    const fixed = (digits: number) =>
        new Intl.NumberFormat(undefined, {
            minimumFractionDigits: digits,
            maximumFractionDigits: digits,
        }).format(value);

    switch (unit) {
        case 'signed_percent':
            return `${value > 0 ? '+' : ''}${fixed(1)}%`;
        case 'percent':
            return `${fixed(1)}%`;
        case 'currency':
            return new Intl.NumberFormat(undefined, {
                style: 'currency',
                currency,
                maximumFractionDigits: 0,
            }).format(value);
        case 'multiple':
            return `${fixed(1)}x`;
        case 'days':
            return `${fixed(0)} days`;
        case 'months':
            return `${fixed(1)} mo`;
        case 'score':
            return `${value > 0 ? '+' : ''}${fixed(0)}`;
        default:
            return fixed(1);
    }
}

export function assess(
    model: GrowBetterModel,
    priorPillarScores: Record<string, number | null> = {},
    priorOverall: number | null = null,
): GrowBetterAssessment {
    const pillars = model.pillars.map((pillar) => {
        const kpis = pillar.kpis.map((kpi) => {
            const score =
                kpi.enabled && kpi.value !== null
                    ? metricScore(
                          kpi.value,
                          kpi.direction,
                          kpi.floor,
                          kpi.target,
                      )
                    : null;

            return {
                ...kpi,
                score,
                missing: kpi.enabled && kpi.value === null,
            };
        });
        const scored = kpis.filter((kpi) => kpi.score !== null);
        const weight = scored.reduce((sum, kpi) => sum + kpi.weight, 0);
        const score =
            weight === 0
                ? null
                : scored.reduce(
                      (sum, kpi) => sum + kpi.weight * (kpi.score ?? 0),
                      0,
                  ) / weight;
        const prior = priorPillarScores[pillar.key] ?? null;

        return {
            key: pillar.key,
            name: pillar.name,
            optional: pillar.optional,
            color: pillar.color,
            help: pillar.help,
            weight: model.weights[pillar.key] ?? 0,
            score,
            display_score: score === null ? null : Math.round(score),
            delta:
                score !== null && prior !== null
                    ? Math.round(score) - Math.round(prior)
                    : null,
            active_kpi_count: kpis.filter(
                (kpi) => kpi.enabled && kpi.value !== null,
            ).length,
            kpi_count: kpis.length,
            kpis,
        };
    });

    const included = pillars.filter(
        (pillar) => pillar.weight > 0 && pillar.score !== null,
    );
    const totalWeight = included.reduce(
        (sum, pillar) => sum + pillar.weight,
        0,
    );
    const configuredWeight = pillars.reduce(
        (sum, pillar) => sum + pillar.weight,
        0,
    );
    const overall =
        totalWeight === 0
            ? 0
            : Math.round(
                  (10 *
                      included.reduce(
                          (sum, pillar) =>
                              sum + pillar.weight * (pillar.score ?? 0),
                          0,
                      )) /
                      totalWeight,
              );

    const scoredPillars = pillars.map((pillar) => ({
        ...pillar,
        contribution:
            totalWeight > 0 && pillar.score !== null && pillar.weight > 0
                ? Math.round(
                      1000 *
                          (pillar.weight / totalWeight) *
                          (pillar.score / 100),
                  )
                : 0,
    }));
    const grade = gradeFor(overall);
    const actions = weakest(scoredPillars);

    return {
        overall,
        grade: grade.grade,
        stage: grade.stage,
        delta:
            priorOverall === null ? null : overall - Math.round(priorOverall),
        total_weight: configuredWeight,
        pillars: scoredPillars,
        actions,
        alerts: alertsFor(actions, scoredPillars, overall, grade.grade),
    };
}

function weakest(
    pillars: GrowBetterAssessment['pillars'],
): GrowBetterAssessment['actions'] {
    const candidates = pillars
        .filter((pillar) => pillar.weight > 0)
        .flatMap((pillar) =>
            pillar.kpis
                .filter((kpi) => kpi.score !== null)
                .map((kpi) => ({
                    key: kpi.key,
                    name: kpi.name,
                    pillar: pillar.name,
                    display: kpi.display ?? '—',
                    meta: kpi.meta,
                    score: kpi.score ?? 0,
                    gain: pointGain(kpi.score ?? 0),
                    help: kpi.help,
                    direction: kpi.direction,
                })),
        )
        .sort(
            (left, right) =>
                left.score - right.score || left.name.localeCompare(right.name),
        );

    return candidates.slice(0, 5).map((candidate, index) => ({
        ...candidate,
        rank: index + 1,
    }));
}

function alertsFor(
    actions: GrowBetterAssessment['actions'],
    pillars: GrowBetterAssessment['pillars'],
    overall: number,
    grade: string,
): GrowBetterAssessment['alerts'] {
    const alerts: GrowBetterAssessment['alerts'] = actions
        .slice(0, 2)
        .map((action) => ({
            tone: action.score < 55 ? 'red' : 'amber',
            text: `${action.name} at ${action.display} — ${action.meta}`,
            context: action.pillar,
        }));

    const strongest = pillars
        .filter((pillar) => pillar.weight > 0)
        .flatMap((pillar) =>
            pillar.kpis
                .filter((kpi) => kpi.score !== null)
                .map((kpi) => ({
                    name: kpi.name,
                    display: kpi.display ?? '—',
                    score: kpi.score ?? 0,
                    pillar: pillar.name,
                })),
        )
        .sort((left, right) => right.score - left.score)[0];

    if (strongest) {
        alerts.push({
            tone: 'green',
            text: `${strongest.name} strong at ${strongest.display}`,
            context: strongest.pillar,
        });
    }

    alerts.push({
        tone: 'green',
        text: `Overall Growth Score ${overall} / 1000 · ${grade}`,
        context: 'This period',
    });

    return alerts;
}
