import {
    createLinkFlowError,
    isErrPayload,
    missingDeviceError,
    missingSupportedAppError,
    unavailableDeviceError,
} from "./utils";
import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context";
import { getLinkController } from "@pkg/logic/domains/link/controller";
import { prepareOrchestratedSession } from "./orchestrator.service";
import { formatErrorDetail, logDomainEvent } from "@pkg/logger";
import { DeviceStatus } from "@pkg/logic/domains/device/data";
import { ERROR_CODES, type Err } from "@pkg/result";
import { settings } from "@pkg/settings";
import { WS_SCRCPY_URL } from "$lib/core/constants";
import { getDeviceController } from "@pkg/logic/domains/device/controller";

const lc = getLinkController();
const dc = getDeviceController();

type LinkSessionShape = {
    id: number;
    token: string;
    status: string;
    expiresAt: Date | null;
    device: {
        id: number;
        title: string;
        status: string;
        inUse: boolean;
        host: string;
        wsPort: string;
    };
    supportedApp: {
        id: number;
        title: string;
        packageName: string;
    };
};

const SCRCPY_SERVER_PORT = 8886;

function buildStreamUrl(host: string, wsPort: string): string | null {
    const trimmedHost = host.trim();
    const trimmedPort = wsPort.trim();

    if (!trimmedHost || !trimmedPort) return null;

    const udid = `${trimmedHost}:${trimmedPort}`;
    const baseWss = WS_SCRCPY_URL.replace(/^https:/, "wss:").replace(
        /^http:/,
        "ws:",
    );
    const wsParam =
        `${baseWss}/?action=proxy-adb&remote=${encodeURIComponent(`tcp:${SCRCPY_SERVER_PORT}`)}&udid=${encodeURIComponent(udid)}`;

    return `${WS_SCRCPY_URL}/#!action=stream&udid=${encodeURIComponent(udid)}&player=mse&ws=${encodeURIComponent(wsParam)}`;
}

async function getPreparedLinkContext(fctx: FlowExecCtx, token: string) {
    const linkResult = await lc.validate(fctx, token);
    if (linkResult.isErr()) {
        return { data: null, error: linkResult.error } as const;
    }

    const link = linkResult.value;

    if (!link.device) {
        return {
            data: null,
            error: missingDeviceError(fctx.flowId, token),
        } as const;
    }

    if (!link.supportedApp) {
        return {
            data: null,
            error: missingSupportedAppError(fctx.flowId, token),
        } as const;
    }

    return {
        data: {
            id: link.id,
            token: link.token,
            status: link.status,
            expiresAt: link.expiresAt,
            device: {
                id: link.device.id,
                title: link.device.title,
                status: link.device.status,
                inUse: link.device.inUse,
                host: link.device.host,
                wsPort: link.device.wsPort,
            },
            supportedApp: {
                id: link.supportedApp.id,
                title: link.supportedApp.title,
                packageName: link.supportedApp.packageName,
            },
        } satisfies LinkSessionShape,
        error: null,
    } as const;
}

export async function resolveLinkFlow(fctx: FlowExecCtx, token: string) {
    logDomainEvent({
        event: "front.link_resolve.started",
        fctx,
        meta: { token },
    });

    const prepared = await getPreparedLinkContext(fctx, token);
    if (prepared.error) {
        logDomainEvent({
            level: "warn",
            event: "front.link_resolve.rejected",
            fctx,
            error: prepared.error,
            meta: { token },
        });

        return { data: null, error: prepared.error };
    }

    const link = prepared.data;

    return {
        data: {
            link: {
                id: link.id,
                token: link.token,
                status: link.status,
                expiresAt: link.expiresAt,
            },
            device: {
                id: link.device.id,
                title: link.device.title,
                status: link.device.status,
                inUse: link.device.inUse,
                isAvailable:
                    link.device.status === DeviceStatus.ONLINE &&
                    !link.device.inUse,
                streamUrl: buildStreamUrl(link.device.host, link.device.wsPort),
            },
            supportedApp: link.supportedApp,
        },
        error: null,
    };
}

export async function prepareLinkFlow(fctx: FlowExecCtx, token: string) {
    logDomainEvent({
        event: "front.link_prepare.started",
        fctx,
        meta: { token },
    });

    const prepared = await getPreparedLinkContext(fctx, token);
    if (prepared.error) {
        logDomainEvent({
            level: "warn",
            event: "front.link_prepare.rejected",
            fctx,
            error: prepared.error,
            meta: { token },
        });

        return { data: null, error: prepared.error };
    }

    const link = prepared.data;

    if (link.device.status !== DeviceStatus.ONLINE || link.device.inUse) {
        return {
            data: null,
            error: unavailableDeviceError(fctx.flowId, link.device),
        };
    }

    try {
        const session = await prepareOrchestratedSession(fctx.flowId, {
            deviceId: link.device.id,
            packageName: link.supportedApp.packageName,
            linkToken: link.token,
        });

        logDomainEvent({
            event: "front.link_prepare.succeeded",
            fctx,
            meta: {
                token,
                deviceId: link.device.id,
                packageName: link.supportedApp.packageName,
            },
        });

        return {
            data: {
                link: {
                    id: link.id,
                    token: link.token,
                },
                device: {
                    id: link.device.id,
                    title: link.device.title,
                    host: link.device.host,
                    wsPort: link.device.wsPort,
                },
                supportedApp: link.supportedApp,
                session: session.data,
            },
            error: null,
        };
    } catch (error) {
        const err: Err = isErrPayload(error)
            ? error
            : createLinkFlowError(
                  fctx.flowId,
                  ERROR_CODES.EXTERNAL_SERVICE_ERROR,
                  "Failed to prepare session",
                  "The front server could not prepare the assigned Android session",
                  formatErrorDetail(error),
              );

        logDomainEvent({
            level: "error",
            event: "front.link_prepare.failed",
            fctx,
            error: err,
            meta: {
                token,
                orchestratorUrl: settings.orchestratorApiUrl,
            },
        });

        return {
            data: null,
            error: err.flowId
                ? err
                : {
                      ...err,
                      flowId: fctx.flowId,
                  },
        };
    }
}

export async function checkLinkValidityFlow(fctx: FlowExecCtx, token: string) {
    logDomainEvent({
        event: "front.link_validity_check.started",
        fctx,
        meta: { token },
    });

    const linkResult = await lc.validateReadOnly(fctx, token);
    if (linkResult.isErr()) {
        logDomainEvent({
            level: "warn",
            event: "front.link_validity_check.rejected",
            fctx,
            error: linkResult.error,
            meta: { token },
        });

        return { data: null, error: linkResult.error };
    }

    const link = linkResult.value;
    const streamUrl = link.device
        ? buildStreamUrl(link.device.host, link.device.wsPort)
        : null;

    return {
        data: {
            link: {
                id: link.id,
                token: link.token,
                status: link.status,
                expiresAt: link.expiresAt,
            },
            device: link.device
                ? {
                      id: link.device.id,
                      streamUrl,
                  }
                : null,
        },
        error: null,
    };
}

export async function endLinkSessionFlow(fctx: FlowExecCtx, token: string) {
    logDomainEvent({
        event: "front.link_session_end.started",
        fctx,
        meta: { token },
    });

    const linkResult = await lc.getByToken(fctx, token);
    if (linkResult.isErr()) {
        logDomainEvent({
            level: "warn",
            event: "front.link_session_end.link_lookup_failed",
            fctx,
            error: linkResult.error,
            meta: { token },
        });

        return { data: null, error: linkResult.error };
    }

    const linkedDeviceId = linkResult.value.linkedDeviceId;
    if (linkedDeviceId === null) {
        return {
            data: { released: false, reason: "no_linked_device" as const },
            error: null,
        };
    }

    const releaseResult = await dc.release(fctx, linkedDeviceId);
    if (releaseResult.isErr()) {
        logDomainEvent({
            level: "error",
            event: "front.link_session_end.release_failed",
            fctx,
            error: releaseResult.error,
            meta: {
                token,
                deviceId: linkedDeviceId,
            },
        });

        return { data: null, error: releaseResult.error };
    }

    return {
        data: { released: true, deviceId: linkedDeviceId },
        error: null,
    };
}
