import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
import { formatErrorDetail } from "@pkg/logger";
import { ERROR_CODES, errorStatusMap, type Err } from "@pkg/result";
import { settings } from "@pkg/settings";
import { randomUUID } from "node:crypto";
import type { MiddlewareHandler } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import * as v from "valibot";

export type AppBindings = {
    Variables: {
        fctx: FlowExecCtx;
    };
};

export function buildFlowExecCtx(flowId?: string): FlowExecCtx {
    return { flowId: flowId || randomUUID() };
}

export function createAppError(
    fctx: FlowExecCtx,
    code: string,
    message: string,
    description: string,
    detail: string,
    actionable?: boolean,
): Err {
    return {
        flowId: fctx.flowId,
        code,
        message,
        description,
        detail,
        actionable,
    };
}

export function jsonError(error: Err, status?: number) {
    return {
        body: {
            data: null,
            error: {
                ...error,
            },
        },
        status:
            status ||
            errorStatusMap[error.code] ||
            errorStatusMap[ERROR_CODES.INTERNAL_SERVER_ERROR] ||
            500,
    };
}

export function toStatusCode(status: number): ContentfulStatusCode {
    return status as ContentfulStatusCode;
}

export function isErrPayload(value: unknown): value is Err {
    return !!value && typeof value === "object" && "code" in value;
}

export function parseDeviceId(rawId: string, fctx: FlowExecCtx): number | Err {
    const parsed = Number(rawId);

    if (!Number.isInteger(parsed) || parsed <= 0) {
        return createAppError(
            fctx,
            ERROR_CODES.VALIDATION_ERROR,
            "Invalid device id",
            "The provided device id is not valid",
            `deviceId=${rawId}`,
            true,
        );
    }

    return parsed;
}

export async function parseJsonBody<TInput, TSchema extends v.GenericSchema<TInput>>(
    request: Request,
    schema: TSchema,
    fctx: FlowExecCtx,
): Promise<TInput | Err> {
    let payload: unknown;

    try {
        payload = await request.json();
    } catch (error) {
        return createAppError(
            fctx,
            ERROR_CODES.VALIDATION_ERROR,
            "Invalid JSON body",
            "The request body must be valid JSON",
            formatErrorDetail(error),
            true,
        );
    }

    const parsed = v.safeParse(schema, payload);
    if (!parsed.success) {
        return createAppError(
            fctx,
            ERROR_CODES.VALIDATION_ERROR,
            "Invalid request body",
            "The request payload did not match the expected shape",
            parsed.issues.map((issue) => issue.message).join("; "),
            true,
        );
    }

    return parsed.output;
}

export const requireInternalApiKey: MiddlewareHandler<AppBindings> = async (
    c,
    next,
) => {
    const fctx = buildFlowExecCtx(c.req.header("x-flow-id"));
    c.set("fctx", fctx);

    const apiKey = c.req.header("x-internal-api-key");
    if (apiKey !== settings.internalApiKey) {
        const error = createAppError(
            fctx,
            ERROR_CODES.AUTH_ERROR,
            "Unauthorized",
            "The internal API key is invalid",
            "Missing or invalid x-internal-api-key header",
            true,
        );
        const response = jsonError(error, 403);
        return c.json(response.body, toStatusCode(response.status));
    }

    await next();
};
