import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';

import { initLearnCard } from '@learncard/init';
import type { EmptyLearnCard, LearnCardFromSeed, DidWebLearnCardFromSeed } from '@learncard/init';

import { getSigningAuthorityForDid } from '@accesslayer/signing-authority/read';
import { getLRUCache } from '@cache/in-memory-lru';

const cloud = process.env.LEARN_CLOUD_URL
    ? { url: process.env.LEARN_CLOUD_URL }
    : { url: 'https://cloud.learncard.com/trpc' };

let emptyLearnCard: EmptyLearnCard['returnValue'];
let learnCard: LearnCardFromSeed['returnValue'];

const saCardsCache = getLRUCache<
    LearnCardFromSeed['returnValue'] | DidWebLearnCardFromSeed['returnValue']
>();
const didWebCardsCache = getLRUCache<DidWebLearnCardFromSeed['returnValue']>();
const ephemeralCardsCache = getLRUCache<LearnCardFromSeed['returnValue']>();

// The DIDKit WASM is copied next to the compiled handler at build time (see
// esbuildPlugins.cjs). The Lambda bundle's node_modules layout doesn't match what
// require.resolve expects (the package is a hoisted workspace symlink), so prefer the
// co-located copy and fall back to package resolution for local dev / Docker.
const DIDKIT_WASM_SPECIFIER = '@learncard/didkit-plugin/dist/didkit_wasm_bg.wasm';

const resolveDidkitWasmPath = (): string => {
    const colocated = join(__dirname, 'didkit_wasm_bg.wasm');
    if (existsSync(colocated)) return colocated;

    return require.resolve(DIDKIT_WASM_SPECIFIER);
};

// Which DIDKit engine actually loaded — exposed via the deep health check so
// native-vs-wasm is observable from an HTTP probe instead of CloudWatch spelunking.
let didKitEngine: 'native' | 'wasm' | 'unloaded' = 'unloaded';
export const getDidKitEngine = (): 'native' | 'wasm' | 'unloaded' => didKitEngine;

// Try native plugin first, fall back to WASM
let didKitInitPromise: Promise<'node' | Buffer> | null = null;

const resolveDidKitPluginFactory = (module: Record<string, unknown>): (() => Promise<unknown>) => {
    const factory =
        (module as { getDidKitPlugin?: unknown }).getDidKitPlugin ??
        (module as { default?: { getDidKitPlugin?: unknown } }).default?.getDidKitPlugin;

    if (typeof factory !== 'function') {
        throw new Error('DIDKit plugin factory not found in module exports');
    }

    return factory as () => Promise<unknown>;
};

const getDidKitInit = async (): Promise<'node' | Buffer> => {
    if (didKitInitPromise) return didKitInitPromise;

    didKitInitPromise = (async () => {
        if (process.env.SKIP_DIDKIT_NAPI) {
            const wasmBuffer = await readFile(resolveDidkitWasmPath());
            didKitEngine = 'wasm';
            return wasmBuffer;
        }

        try {
            const didkitModule = await import('@learncard/didkit-plugin-node');
            const getNativePlugin = resolveDidKitPluginFactory(didkitModule);
            await getNativePlugin();
            didKitEngine = 'native';
            return 'node' as const;
        } catch (error) {
            // Surface the fallback — a silent catch here hid a months-long "native never
            // actually loads in Lambda" gap (see PR #1341 investigation).
            console.warn('[didkit] native plugin unavailable, falling back to WASM:', error);
            const wasmBuffer = await readFile(resolveDidkitWasmPath());
            didKitEngine = 'wasm';
            return wasmBuffer;
        }
    })();

    return didKitInitPromise;
};

export const getEmptyLearnCard = async (): Promise<EmptyLearnCard['returnValue']> => {
    if (!emptyLearnCard)
        emptyLearnCard = await initLearnCard({
            didkit: await getDidKitInit(),
        });

    return emptyLearnCard;
};

export const getLearnCard = async (): Promise<LearnCardFromSeed['returnValue']> => {
    const seed = process.env.SEED;

    if (!seed) throw new Error('No seed set!');

    if (!learnCard)
        learnCard = await initLearnCard({
            didkit: await getDidKitInit(),
            seed,
            cloud,
        });

    return learnCard;
};

export const getSigningAuthorityLearnCard = async (
    ownerDID: string,
    name: string
): Promise<DidWebLearnCardFromSeed['returnValue'] | LearnCardFromSeed['returnValue']> => {
    console.log('[LCA getSigningAuthorityLearnCard] Looking up SA:', { ownerDID, name });

    const sa = await getSigningAuthorityForDid(ownerDID, name);

    if (!sa?.seed) {
        console.error('[LCA getSigningAuthorityLearnCard] SA not found or has no seed:', {
            ownerDID,
            name,
            saFound: !!sa,
            hasSeed: !!sa?.seed,
        });
        throw new Error(`No signing authority found for ownerDID="${ownerDID}" name="${name}"`);
    }

    const cachedValue = saCardsCache.get(sa.seed);

    if (cachedValue) {
        console.log('[LCA getSigningAuthorityLearnCard] Using cached SA LearnCard');
        return cachedValue;
    }

    console.log('[LCA getSigningAuthorityLearnCard] Initializing SA LearnCard:', {
        isDidWeb: ownerDID.startsWith('did:web:'),
        ownerDID,
    });

    const saLearnCard = ownerDID.startsWith('did:web:')
        ? await initLearnCard({
              didkit: await getDidKitInit(),
              seed: sa.seed,
              didWeb: ownerDID,
              cloud,
              allowRemoteContexts: true,
          })
        : await initLearnCard({
              didkit: await getDidKitInit(),
              seed: sa.seed,
              cloud,
              allowRemoteContexts: true,
          });

    saCardsCache.add(sa.seed, saLearnCard);

    return saLearnCard;
};

export const getServerDidWebDID = (): string => {
    const domainName = process.env.DOMAIN_NAME;
    const domain =
        !domainName || process.env.IS_OFFLINE
            ? `localhost%3A${process.env.PORT || 3000}`
            : domainName;
    return `did:web:${domain}`;
};

export const getDidWebLearnCard = async (
    seed?: string,
    didWeb?: string
): Promise<DidWebLearnCardFromSeed['returnValue']> => {
    const _seed = seed || process.env.SEED;
    const _didWeb = didWeb || getServerDidWebDID();
    if (!_seed) throw new Error('No seed set!');
    if (!_didWeb) throw new Error('No didWeb set!');

    const cachedValue = didWebCardsCache.get(`${_seed}|${_didWeb}`);

    if (cachedValue) return cachedValue;

    const didWebLearnCard = await initLearnCard({
        didkit: await getDidKitInit(),
        seed: _seed,
        didWeb: _didWeb,
        cloud,
    });

    didWebCardsCache.add(`${_seed}|${_didWeb}`, didWebLearnCard);

    return didWebLearnCard;
};

export const getEphemeralLearnCard = async (
    seed: string
): Promise<LearnCardFromSeed['returnValue']> => {
    const cachedValue = ephemeralCardsCache.get(seed);

    if (cachedValue) return cachedValue;

    const ephemeralLearnCard = await initLearnCard({
        didkit: await getDidKitInit(),
        seed,
        cloud,
    });

    ephemeralCardsCache.add(seed, ephemeralLearnCard);

    return ephemeralLearnCard;
};
