import { prisma } from '@/lib/core/prisma';
import { parseTestCaseJson, cleanStepsForStorage, normalizeTargetConfigMap } from '@/lib/runtime/test-case-utils';
import { getTeamDevicesAvailability } from '@/lib/runners/availability-service';
import { resolveConfigs } from '@/lib/test-config/resolver';
import { hasTemplatedConfigUrls, validateConfigUrls } from '@/lib/test-config/url-validation';
import {
    ANDROID_EXECUTION_CAPABILITY,
    ANDROID_EXECUTION_RUNNER_KIND,
    BROWSER_EXECUTION_CAPABILITY,
} from '@/lib/runners/constants';
import { isAndroidTargetConfig, normalizeAndroidTargetConfig } from '@/lib/android/target-config';
import {
    collectAndroidRequestedDeviceIds,
    collectAndroidRequestedRunnerIds,
    extractRequestedDeviceId,
    extractRequestedRunnerId,
    hasAndroidTargets,
    isEmulatorProfileInventoryDevice,
} from '@/lib/android/target-requests';
import { RUN_TRIGGER_SOURCE, TEST_CASE_KIND, TEST_STATUS, type BrowserConfig, type ResolvedConfig, type RunTriggerSource, type TargetConfig, type TestStep } from '@/types';
import { createRunSession, resolveLoginFlowIds } from '@/lib/runtime/run-session-service';

function validateAndroidTargets(
    browserConfig: Record<string, BrowserConfig | TargetConfig> | undefined
): string | null {
    if (!browserConfig || Object.keys(browserConfig).length === 0) {
        return null;
    }

    const androidTargets = Object.values(browserConfig).filter(isAndroidTargetConfig);
    if (androidTargets.length === 0) {
        return null;
    }

    for (const target of androidTargets) {
        const normalizedTarget = normalizeAndroidTargetConfig(target);
        const selector = normalizedTarget.deviceSelector;

        if (selector.mode === 'emulator-profile' && !selector.emulatorProfileName) {
            return 'Android target must include a device';
        }
        if (selector.mode === 'connected-device' && !selector.serial) {
            return 'Android target must include a device';
        }
        if (!target.appId) {
            return 'Android target must include an app ID';
        }
        if (typeof target.clearAppState !== 'boolean') {
            return 'Android target clearAppState must be a boolean';
        }
        if (typeof target.allowAllPermissions !== 'boolean') {
            return 'Android target allowAllPermissions must be a boolean';
        }
    }

    return null;
}

function createConfigurationSnapshot(config: {
    url?: string;
    prompt?: string;
    steps?: TestStep[];
    browserConfig?: Record<string, BrowserConfig | TargetConfig>;
    requestedDeviceId?: string | null;
    requestedRunnerId?: string | null;
    resolvedConfigurations?: ResolvedConfig[];
}) {
    return config;
}

export interface RunTestOverrides {
    url?: string;
    prompt?: string;
    steps?: TestStep[];
    browserConfig?: Record<string, BrowserConfig | TargetConfig>;
    requestedDeviceId?: string;
    requestedRunnerId?: string;
}

export interface QueueTestCaseRunResult {
    runId: string;
    status: string;
    requiredCapability: string | null;
    requestedDeviceId: string | null;
    requestedRunnerId: string | null;
}

export interface QueueTestCaseRunFailure {
    error: string;
    details?: unknown;
}

export interface QueueTestCaseRunTrigger {
    source: RunTriggerSource;
}

export async function queueTestCaseRun(
    userId: string,
    testCaseId: string,
    overrides?: RunTestOverrides,
    trigger: QueueTestCaseRunTrigger = { source: RUN_TRIGGER_SOURCE.USER }
): Promise<{ ok: true; data: QueueTestCaseRunResult } | { ok: false; failure: QueueTestCaseRunFailure }> {
    const user = await prisma.user.findUnique({
        where: { id: userId },
        select: { email: true },
    });
    const triggeredByEmail = trigger.source === RUN_TRIGGER_SOURCE.SCHEDULER
        ? null
        : (typeof user?.email === 'string' ? user.email : null);

    const testCase = await prisma.testCase.findUnique({
        where: { id: testCaseId },
        include: {
            files: {
                select: { filename: true, storedName: true, mimeType: true, size: true },
            },
            project: {
                select: {
                    teamId: true,
                    team: {
                        select: {
                            openRouterKeyEncrypted: true,
                            memberships: {
                                where: { userId },
                                select: { id: true },
                                take: 1,
                            }
                        }
                    }
                }
            }
        }
    });

    if (!testCase) {
        return { ok: false, failure: { error: 'Test case not found' } };
    }

    if (testCase.project.team.memberships.length === 0) {
        return { ok: false, failure: { error: 'Forbidden' } };
    }

    if (!testCase.project.team.openRouterKeyEncrypted) {
        return { ok: false, failure: { error: 'Please configure this team OpenRouter API key' } };
    }

    const parsedTestCase = parseTestCaseJson({
        url: testCase.url,
        prompt: testCase.prompt,
        steps: testCase.steps,
        browserConfig: testCase.browserConfig,
    });

    const normalizedSteps = overrides?.steps ? cleanStepsForStorage(overrides.steps) : parsedTestCase.steps;
    const normalizedBrowserConfig = overrides?.browserConfig
        ? normalizeTargetConfigMap(overrides.browserConfig)
        : parsedTestCase.browserConfig;
    const normalizedPrompt = overrides?.prompt ?? parsedTestCase.prompt ?? undefined;
    const normalizedUrl = overrides?.url ?? parsedTestCase.url;

    const hasBrowserConfig = !!normalizedBrowserConfig && Object.keys(normalizedBrowserConfig).length > 0;
    if (!hasBrowserConfig && !normalizedUrl) {
        return { ok: false, failure: { error: 'Valid configuration (URL or BrowserConfig) is required' } };
    }
    if ((!normalizedSteps || normalizedSteps.length === 0) && !normalizedPrompt) {
        return { ok: false, failure: { error: 'Instructions (Prompt or Steps) are required' } };
    }

    const requiresResolvedVariables = hasTemplatedConfigUrls({ url: normalizedUrl, browserConfig: normalizedBrowserConfig });
    let resolvedVariables: Record<string, string> = {};
    let resolvedConfigurations: ResolvedConfig[] = [];
    try {
        const resolvedConfigs = await resolveConfigs(testCase.projectId, testCaseId);
        resolvedConfigurations = resolvedConfigs.allConfigs;
        if (requiresResolvedVariables) {
            resolvedVariables = resolvedConfigs.variables;
        }
    } catch (error) {
        if (requiresResolvedVariables) {
            throw error;
        }
    }

    const urlValidationError = validateConfigUrls(
        {
            url: normalizedUrl,
            browserConfig: normalizedBrowserConfig,
        },
        resolvedVariables
    );
    if (urlValidationError) {
        return { ok: false, failure: { error: urlValidationError } };
    }

    const androidValidationError = validateAndroidTargets(normalizedBrowserConfig);
    if (androidValidationError) {
        return { ok: false, failure: { error: androidValidationError } };
    }

    const requestHasAndroidTargets = hasAndroidTargets(normalizedBrowserConfig);
    const requestedDeviceIdInput = typeof overrides?.requestedDeviceId === 'string'
        ? overrides.requestedDeviceId.trim()
        : '';
    const requestedRunnerIdInput = typeof overrides?.requestedRunnerId === 'string'
        ? overrides.requestedRunnerId.trim()
        : '';

    if (!requestHasAndroidTargets && requestedDeviceIdInput) {
        return { ok: false, failure: { error: 'requestedDeviceId requires Android targets' } };
    }

    if (!requestHasAndroidTargets && requestedRunnerIdInput) {
        return { ok: false, failure: { error: 'requestedRunnerId requires Android targets' } };
    }

    const inferredRequestedDeviceId = extractRequestedDeviceId(normalizedBrowserConfig);
    const androidRequestedDeviceIds = collectAndroidRequestedDeviceIds(normalizedBrowserConfig);
    const inferredRequestedRunnerId = extractRequestedRunnerId(normalizedBrowserConfig);
    const androidRequestedRunnerIds = collectAndroidRequestedRunnerIds(normalizedBrowserConfig);
    const requestedDeviceId = requestHasAndroidTargets
        ? (requestedDeviceIdInput || inferredRequestedDeviceId)
        : null;
    const requestedRunnerId = requestHasAndroidTargets
        ? (requestedRunnerIdInput || inferredRequestedRunnerId || null)
        : null;

    if (requestHasAndroidTargets && !requestedDeviceId) {
        return {
            ok: false,
            failure: {
                error: 'Android runs require a single requestedDeviceId. Align Android target selectors or provide requestedDeviceId override.'
            },
        };
    }

    if (
        requestHasAndroidTargets
        && requestedDeviceIdInput
        && !androidRequestedDeviceIds.has(requestedDeviceIdInput)
    ) {
        return { ok: false, failure: { error: 'requestedDeviceId must match an Android target device selector' } };
    }

    if (
        requestHasAndroidTargets
        && requestedRunnerIdInput
        && androidRequestedRunnerIds.size > 0
        && !androidRequestedRunnerIds.has(requestedRunnerIdInput)
    ) {
        return { ok: false, failure: { error: 'requestedRunnerId must match an Android target runner scope' } };
    }
    if (
        requestHasAndroidTargets
        && !requestedRunnerIdInput
        && androidRequestedRunnerIds.size > 1
    ) {
        return {
            ok: false,
            failure: {
                error: 'Android targets specify multiple runner scopes; provide requestedRunnerId override or align target runnerScope values'
            }
        };
    }

    if (requestHasAndroidTargets && requestedDeviceId) {
        const availability = await getTeamDevicesAvailability(testCase.project.teamId);
        const selectedDevice = requestedRunnerId
            ? availability.devices.find((device) => (
                device.deviceId === requestedDeviceId && device.runnerId === requestedRunnerId
            ))
            : availability.devices.find((device) => device.deviceId === requestedDeviceId);

        const emulatorProfileClaimable = selectedDevice
            && isEmulatorProfileInventoryDevice(selectedDevice)
            && selectedDevice.isFresh
            && availability.runnerConnected;

        if (!selectedDevice || (!selectedDevice.isAvailable && !emulatorProfileClaimable)) {
            return {
                ok: false,
                failure: {
                    error: 'Selected device is no longer available. Check Team Settings > Runners and choose an available device.'
                }
            };
        }
    }

    const configurationSnapshot = JSON.stringify(createConfigurationSnapshot({
        url: normalizedUrl,
        prompt: normalizedPrompt,
        steps: normalizedSteps,
        browserConfig: normalizedBrowserConfig,
        requestedDeviceId,
        requestedRunnerId,
        resolvedConfigurations,
    }));

    const requiredCapability = requestHasAndroidTargets
        ? ANDROID_EXECUTION_CAPABILITY
        : BROWSER_EXECUTION_CAPABILITY;
    const runSessionId = await createRunSession({
        projectId: testCase.projectId,
        requiredCapability,
        triggeredByEmail,
        triggerSource: trigger.source,
    });

    const loginFlowIds = requestHasAndroidTargets
        ? []
        : await resolveLoginFlowIds(testCase.projectId, normalizedBrowserConfig);
    let testSessionPosition = 0;
    for (const loginFlowId of loginFlowIds) {
        await prisma.testRun.create({
            data: {
                testCaseId: loginFlowId,
                runSessionId,
                sessionPosition: testSessionPosition,
                kind: TEST_CASE_KIND.LOGIN_FLOW,
                status: TEST_STATUS.QUEUED,
                requiredCapability: BROWSER_EXECUTION_CAPABILITY,
                triggeredByEmail,
                triggerSource: trigger.source,
            },
        });
        testSessionPosition += 1;
    }

    const testRun = await prisma.testRun.create({
        data: {
            testCaseId,
            runSessionId,
            sessionPosition: testSessionPosition,
            kind: testCase.kind,
            status: TEST_STATUS.QUEUED,
            configurationSnapshot,
            requiredCapability,
            requiredRunnerKind: requestHasAndroidTargets
                ? ANDROID_EXECUTION_RUNNER_KIND
                : null,
            requestedDeviceId,
            requestedRunnerId,
            triggeredByEmail,
            triggerSource: trigger.source,
        }
    });

    if (testCase.files.length > 0) {
        await prisma.testRunFile.createMany({
            data: testCase.files.map((file) => ({
                runId: testRun.id,
                filename: file.filename,
                storedName: file.storedName,
                mimeType: file.mimeType,
                size: file.size,
            }))
        });
    }
    return {
        ok: true,
        data: {
            runId: testRun.id,
            status: testRun.status,
            requiredCapability: testRun.requiredCapability,
            requestedDeviceId: testRun.requestedDeviceId,
            requestedRunnerId: testRun.requestedRunnerId,
        }
    };
}
