import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
import { formatErrorDetail, logDomainEvent } from "@pkg/logger";
import { createAppError, jsonError } from "../../core/utils";
import type { SessionPrepareInput } from "./data";
import { ERROR_CODES } from "@pkg/result";
import { getDeviceController } from "@pkg/logic/domains/device/controller";
import { disconnectDevice, prepareAssignedApp } from "../command/service";

const deviceController = getDeviceController();

export async function prepareSession(
    fctx: FlowExecCtx,
    input: SessionPrepareInput,
) {
    logDomainEvent({
        event: "orchestrator.session_prepare.started",
        fctx,
        meta: {
            deviceId: input.deviceId,
            packageName: input.packageName,
            linkToken: input.linkToken,
        },
    });

    const deviceResult = await deviceController.getById(fctx, input.deviceId);
    if (deviceResult.isErr()) {
        logDomainEvent({
            level: "warn",
            event: "orchestrator.session_prepare.device_lookup_failed",
            fctx,
            error: deviceResult.error,
            meta: {
                deviceId: input.deviceId,
            },
        });

        return jsonError(deviceResult.error);
    }

    const allocatedDevice = await deviceController.allocate(
        fctx,
        input.deviceId,
    );
    if (allocatedDevice.isErr()) {
        logDomainEvent({
            level: "warn",
            event: "orchestrator.session_prepare.device_unavailable",
            fctx,
            error: allocatedDevice.error,
            meta: {
                deviceId: input.deviceId,
            },
        });

        return jsonError(allocatedDevice.error);
    }

    let releaseRequired = true;

    try {
        const prepResult = await prepareAssignedApp(
            allocatedDevice.value,
            input.packageName,
        );

        logDomainEvent({
            event: "orchestrator.session_prepare.succeeded",
            fctx,
            meta: {
                deviceId: input.deviceId,
                packageName: input.packageName,
                serial: prepResult.serial,
            },
        });

        releaseRequired = false;

        return {
            body: {
                data: {
                    deviceId: input.deviceId,
                    containerId: allocatedDevice.value.containerId,
                    packageName: input.packageName,
                    serial: prepResult.serial,
                    status: "prepared",
                },
                error: null,
            },
            status: 200,
        };
    } catch (error) {
        logDomainEvent({
            level: "error",
            event: "orchestrator.session_prepare.failed",
            fctx,
            error,
            meta: {
                deviceId: input.deviceId,
                packageName: input.packageName,
            },
        });

        return jsonError(
            createAppError(
                fctx,
                ERROR_CODES.EXTERNAL_SERVICE_ERROR,
                "Failed to prepare Android session",
                "The orchestrator could not connect to the Android device or launch the app",
                formatErrorDetail(error),
            ),
            502,
        );
    } finally {
        await disconnectDevice(allocatedDevice.value);

        if (releaseRequired) {
            const releaseResult = await deviceController.release(
                fctx,
                input.deviceId,
            );
            if (releaseResult.isErr()) {
                logDomainEvent({
                    level: "error",
                    event: "orchestrator.session_prepare.release_failed",
                    fctx,
                    error: releaseResult.error,
                    meta: {
                        deviceId: input.deviceId,
                    },
                });
            }
        }
    }
}
