"use strict";
/**
 * WebDriverAgent (WDA) Preparation Module
 *
 * This module provides functionality to build, sign, and package WebDriverAgent for iOS devices.
 * It is used via the WebSocket API from the hub UI, not as a CLI command.
 *
 * The prepareWda function is called with the following options:
 *   - provisioningUUID: UUID of the provisioning profile to use (REQUIRED)
 *   - isFreeAccount: Whether this is a free or enterprise account (REQUIRED)
 *   - outputFileName: Custom output filename without .ipa extension (optional, defaults to 'wda-resign')
 *   - uploadToHub: Whether to upload to hub after build (optional, defaults to true)
 *   - setAsDefault: Whether to set as default WDA (optional, defaults to false)
 *   - wdaProjectPath: Path to WebDriverAgent xcode project (optional, auto-detected if not provided)
 *   - platform: Platform type: ios, tvos, or both (optional, defaults to 'ios')
 *   - onProgress: Callback for progress updates (optional)
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProvisioningProfilePath = getProvisioningProfilePath;
exports.prepareWda = prepareWda;
exports.listProvisioningProfiles = listProvisioningProfiles;
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const util_1 = __importDefault(require("util"));
const form_data_1 = __importDefault(require("form-data"));
const node_config_1 = require("../config/node.config");
// ============================================================================
// Helper Functions
// ============================================================================
const execAsync = util_1.default.promisify(child_process_1.exec);
const WDA_BUILD_PATH = '/appium_wda_ios/Build/Products/Debug-iphoneos';
/**
 * Get Xcode major version
 */
async function getXcodeMajorVersion() {
    const { stdout } = await execAsync('xcodebuild -version');
    const match = stdout.match(/Xcode (\d+)\./);
    if (!match) {
        throw new Error('Unable to determine Xcode version');
    }
    return parseInt(match[1], 10);
}
/**
 * Get provisioning profile path based on Xcode version
 */
async function getProvisioningProfilePath() {
    const xcodeVersion = await getXcodeMajorVersion();
    if (xcodeVersion <= 15) {
        return path_1.default.join(os_1.default.homedir(), 'Library/MobileDevice/Provisioning Profiles');
    }
    else {
        return path_1.default.join(os_1.default.homedir(), 'Library/Developer/Xcode/UserData/Provisioning Profiles');
    }
}
/**
 * Load required build dependencies dynamically
 */
function loadDependencies() {
    try {
        // Dynamic imports to handle optional dependencies
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const Applesign = require('applesign');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const archiver = require('archiver');
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const { provision } = require('ios-mobileprovision-finder');
        return { Applesign, archiver, provision };
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        throw new Error(`Missing required dependencies for WDA preparation: ${errorMessage}. ` +
            `Please install: applesign archiver ios-mobileprovision-finder`);
    }
}
/**
 * Get mobile provisioning file path from UUID
 */
async function getMobileProvisioningFilePath(provision, provisioningUUID) {
    const provisionFileDir = await getProvisioningProfilePath();
    if (!fs_1.default.existsSync(provisionFileDir)) {
        throw new Error(`Provisioning directory does not exist: ${provisionFileDir}`);
    }
    const files = fs_1.default
        .readdirSync(provisionFileDir, { encoding: 'utf8' })
        .filter(file => file.endsWith('.mobileprovision'));
    const provisioningFiles = files.map(file => {
        const fullPath = path_1.default.join(provisionFileDir, file);
        const mp = provision.readFromFile(fullPath);
        return { ...mp, _filePath: fullPath };
    });
    if (!provisioningFiles || !provisioningFiles.length) {
        throw new Error('No mobileprovision file found on the machine');
    }
    const selectedProfile = provisioningFiles.find(file => file.UUID === provisioningUUID);
    if (!selectedProfile) {
        throw new Error(`Provisioning profile with UUID ${provisioningUUID} not found`);
    }
    // Use the actual file path from the profile, not a constructed path based on UUID
    // The file on disk may have a different name than the UUID
    return {
        filePath: selectedProfile._filePath,
        profile: selectedProfile,
    };
}
/**
 * Get WebDriverAgent project path
 */
async function getWdaProject(wdaProjectPath) {
    if (wdaProjectPath) {
        if (!fs_1.default.existsSync(wdaProjectPath) || !fs_1.default.statSync(wdaProjectPath).isDirectory()) {
            throw new Error(`Unable to find webdriver agent project in path ${wdaProjectPath}`);
        }
        return wdaProjectPath;
    }
    try {
        const { stdout } = await execAsync('find $HOME/.appium -name WebDriverAgent.xcodeproj');
        const projectPath = stdout.trim().split('\n')[0];
        if (!projectPath) {
            throw new Error('WebDriverAgent.xcodeproj not found');
        }
        return path_1.default.dirname(projectPath);
    }
    catch (err) {
        throw new Error('Unable to find WebDriverAgent project. Install Appium XCUITest driver first.');
    }
}
/**
 * Build WebDriverAgent
 */
async function buildWebDriverAgent(projectDir, bundleId, onProgress) {
    try {
        let buildCommand = 'xcodebuild clean build-for-testing -project WebDriverAgent.xcodeproj -derivedDataPath appium_wda_ios -scheme WebDriverAgentRunner -destination generic/platform=iOS CODE_SIGNING_ALLOWED=NO';
        if (bundleId) {
            buildCommand += ` PRODUCT_BUNDLE_IDENTIFIER=${bundleId.replace(/^\s+|\s+$/g, '')}`;
        }
        if (onProgress) {
            onProgress(`Executing: ${buildCommand}`);
        }
        await execAsync(buildCommand, { cwd: projectDir, maxBuffer: 1024 * 1024 * 100 });
        return `${projectDir}/${WDA_BUILD_PATH}/WebDriverAgentRunner-Runner.app`;
    }
    catch (error) {
        throw new Error(`Error building WebDriverAgent: ${error?.message}`);
    }
}
/**
 * Zip payload directory
 */
async function zipPayloadDirectory(archiver, outputZipPath, folderPath, onProgress) {
    return new Promise((resolve, reject) => {
        const output = fs_1.default.createWriteStream(outputZipPath);
        const archive = archiver('zip', { zlib: { level: 9 } });
        output.on('close', () => {
            if (onProgress) {
                onProgress(`Zipped ${archive.pointer()} total bytes`);
                onProgress(`Archive has been written to ${outputZipPath}`);
            }
            resolve();
        });
        archive.on('error', (err) => {
            reject(err);
        });
        archive.pipe(output);
        archive.directory(folderPath, 'Payload');
        archive.finalize();
    });
}
/**
 * Upload WDA IPA to hub
 */
async function uploadWdaToHub(hubUrl, authHeader, ipaPath, bundleId, setAsDefault = false) {
    return new Promise((resolve, reject) => {
        const url = new URL('/api/wda/upload', hubUrl);
        const client = url.protocol === 'https:' ? https_1.default : http_1.default;
        const form = new form_data_1.default();
        form.append('file', fs_1.default.createReadStream(ipaPath));
        form.append('bundleId', bundleId);
        form.append('setAsDefault', setAsDefault.toString());
        const headers = {
            ...form.getHeaders(),
            Authorization: authHeader,
        };
        const req = client.request({
            method: 'POST',
            hostname: url.hostname,
            port: url.port,
            path: url.pathname,
            headers,
        }, res => {
            let data = '';
            res.on('data', chunk => (data += chunk));
            res.on('end', () => {
                if (res.statusCode === 200 || res.statusCode === 201) {
                    try {
                        const response = JSON.parse(data);
                        resolve({
                            success: true,
                            message: 'WDA uploaded successfully',
                            wdaId: response.wda?.id,
                        });
                    }
                    catch {
                        resolve({ success: true, message: 'WDA uploaded successfully' });
                    }
                }
                else {
                    resolve({ success: false, message: `Upload failed: HTTP ${res.statusCode}` });
                }
            });
        });
        req.on('error', error => {
            reject(error);
        });
        form.pipe(req);
    });
}
/**
 * Check if hub server is running
 */
async function isHubRunning(hubUrl) {
    return new Promise(resolve => {
        const url = new URL('/health', hubUrl);
        const client = url.protocol === 'https:' ? https_1.default : http_1.default;
        const req = client.get(url.href, { timeout: 3000 }, res => {
            resolve(res.statusCode === 200);
        });
        req.on('error', () => resolve(false));
        req.on('timeout', () => {
            req.destroy();
            resolve(false);
        });
    });
}
// ============================================================================
// Main Export Function
// ============================================================================
/**
 * Main function to prepare WDA (non-interactive, API-driven)
 *
 * This function is called via WebSocket from the hub UI. All parameters
 * must be provided - there are no interactive prompts.
 */
async function prepareWda(options) {
    const progress = options.onProgress || (() => { });
    // Validate required parameters
    if (!options.provisioningUUID) {
        throw new Error('provisioningUUID is required');
    }
    if (options.isFreeAccount === undefined) {
        throw new Error('isFreeAccount is required');
    }
    progress('init', 'Starting WebDriverAgent preparation...');
    // Load dependencies
    progress('deps', 'Loading build dependencies...');
    const { Applesign, archiver, provision } = loadDependencies();
    // Get provisioning profile info
    progress('profile', 'Validating provisioning profile...');
    const { filePath: mobileProvisioningFile, profile } = await getMobileProvisioningFilePath(provision, options.provisioningUUID);
    const platform = options.platform || 'ios';
    const customOutputFileName = options.outputFileName || (platform === 'tvos' ? 'wda-resign_tvos' : 'wda-resign');
    // Get bundle ID
    let bundleId;
    if (options.isFreeAccount) {
        bundleId = profile.Name.split(':')[1] || profile.Name;
    }
    else if (options.bundleId) {
        bundleId = options.bundleId;
    }
    else {
        // Enterprise: extract bundleId from provisioning profile's application-identifier
        try {
            const { stdout: profileXml } = await execAsync(`security cms -D -i "${mobileProvisioningFile}" 2>/dev/null`);
            const appIdMatch = profileXml.match(/<key>application-identifier<\/key>\s*<string>([^<]+)<\/string>/);
            if (appIdMatch) {
                // application-identifier is "TEAMID.bundleId" — strip team prefix
                const parts = appIdMatch[1].split('.');
                parts.shift(); // remove team ID
                const profileBundleId = parts.join('.');
                if (profileBundleId && profileBundleId !== '*') {
                    bundleId = profileBundleId;
                    progress('profile', `Extracted bundle ID from profile: ${bundleId}`);
                }
            }
        }
        catch {
            progress('profile', 'Could not extract bundle ID from provisioning profile');
        }
    }
    // Find WDA project
    progress('project', 'Searching for WebDriverAgent.xcodeproj...');
    const wdaProjectPath = await getWdaProject(options.wdaProjectPath);
    progress('project', `Found WebDriverAgent.xcodeproj at: ${wdaProjectPath}`);
    // Build WDA
    progress('build', 'Building WebDriverAgent (this may take several minutes)...', 10);
    const wdaAppPath = await buildWebDriverAgent(wdaProjectPath, bundleId, msg => progress('build', msg));
    progress('build', 'Successfully built WebDriverAgent', 40);
    // Prepare IPA
    progress('package', 'Preparing WebDriverAgent IPA...');
    const wdaBuildPath = path_1.default.join(wdaProjectPath, WDA_BUILD_PATH);
    const payloadDirectory = path_1.default.join(wdaBuildPath, 'Payload');
    // Remove framework directory
    const frameworksPath = `${wdaAppPath}/Frameworks`;
    if (fs_1.default.existsSync(frameworksPath)) {
        fs_1.default.readdirSync(frameworksPath).forEach(f => fs_1.default.rmSync(`${frameworksPath}/${f}`, { recursive: true }));
    }
    // Update Info.plist if using free account
    const infoPlistPath = path_1.default.join(wdaAppPath, 'Info.plist');
    if (fs_1.default.existsSync(infoPlistPath) && bundleId) {
        let infoPlistContent = fs_1.default.readFileSync(infoPlistPath, 'utf8');
        infoPlistContent = infoPlistContent.replace(/<key>CFBundleIdentifier<\/key>\n\s*<string>(.*?)<\/string>/, `<key>CFBundleIdentifier</key>\n<string>${bundleId.replace(/^\s+|\s+$/g, '')}</string>`);
        fs_1.default.writeFileSync(infoPlistPath, infoPlistContent, 'utf8');
    }
    // Create Payload directory and move .app file
    await execAsync(`mkdir -p ${payloadDirectory}`);
    await execAsync(`mv ${wdaAppPath} ${payloadDirectory}`);
    progress('package', 'Created Payload directory', 50);
    // Zip payload
    await zipPayloadDirectory(archiver, `${wdaBuildPath}/wda-resign.zip`, payloadDirectory, msg => progress('package', msg));
    progress('package', 'Packaged WebDriverAgent', 60);
    // Sign IPA
    progress('sign', 'Signing WebDriverAgent IPA...');
    let generatedIpaPath;
    if (platform === 'both') {
        const platforms = ['ios', 'tvos'];
        for (const p of platforms) {
            const wdaFileName = p === 'tvos' ? `${customOutputFileName}_tvos.ipa` : `${customOutputFileName}.ipa`;
            const ipaPath = `${wdaBuildPath}/${wdaFileName}`;
            const appleOptions = {
                mobileprovision: mobileProvisioningFile,
                outfile: ipaPath,
            };
            if (bundleId) {
                appleOptions.bundleId = bundleId.replace(/^\s+|\s+$/g, '');
            }
            const as = new Applesign(appleOptions);
            await as.signIPA(path_1.default.join(wdaBuildPath, 'wda-resign.zip'));
            if (p === 'ios') {
                generatedIpaPath = ipaPath;
            }
        }
        progress('sign', `Signed WebDriverAgent for both platforms`, 80);
    }
    else {
        const wdaFileName = `${customOutputFileName}.ipa`;
        generatedIpaPath = `${wdaBuildPath}/${wdaFileName}`;
        const appleOptions = {
            mobileprovision: mobileProvisioningFile,
            outfile: generatedIpaPath,
        };
        if (bundleId) {
            appleOptions.bundleId = bundleId.replace(/^\s+|\s+$/g, '');
        }
        const as = new Applesign(appleOptions);
        await as.signIPA(path_1.default.join(wdaBuildPath, 'wda-resign.zip'));
        progress('sign', `Signed WebDriverAgent for ${platform}`, 80);
    }
    // Upload to hub if requested (default: true)
    const shouldUpload = options.uploadToHub !== false;
    let wdaId;
    if (shouldUpload) {
        const config = (0, node_config_1.getMergedConfig)();
        const hubUrl = config.hubUrl;
        const accessKey = config.accessKey;
        const secretToken = config.token;
        if (!hubUrl || !accessKey || !secretToken) {
            progress('upload', 'Hub credentials not configured, skipping upload');
        }
        else {
            const hubRunning = await isHubRunning(hubUrl);
            if (!hubRunning) {
                progress('upload', 'Hub server not reachable, skipping upload');
            }
            else {
                progress('upload', 'Uploading WDA to hub...', 85);
                const basicAuth = Buffer.from(`${accessKey}:${secretToken}`).toString('base64');
                const authHeader = `Basic ${basicAuth}`;
                const finalBundleId = bundleId?.replace(/^\s+|\s+$/g, '') || 'com.facebook.WebDriverAgentRunner';
                try {
                    const result = await uploadWdaToHub(hubUrl, authHeader, generatedIpaPath, finalBundleId, options.setAsDefault ?? false);
                    if (result.success) {
                        wdaId = result.wdaId;
                        progress('upload', `WDA uploaded successfully. ID: ${wdaId}`, 100);
                    }
                    else {
                        progress('upload', `Upload failed: ${result.message}`);
                    }
                }
                catch (uploadError) {
                    progress('upload', `Upload error: ${uploadError.message}`);
                }
            }
        }
    }
    progress('complete', 'WebDriverAgent preparation complete!', 100);
    return {
        success: true,
        message: 'WebDriverAgent preparation complete',
        ipaPath: generatedIpaPath,
        wdaId,
        bundleId: bundleId?.replace(/^\s+|\s+$/g, ''),
    };
}
/**
 * List available provisioning profiles on this machine
 * Used by the hub UI to populate the profile selection dropdown
 */
async function listProvisioningProfiles() {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const { provision } = require('ios-mobileprovision-finder');
    const provisionFileDir = await getProvisioningProfilePath();
    if (!fs_1.default.existsSync(provisionFileDir)) {
        return { profiles: [] };
    }
    const files = fs_1.default
        .readdirSync(provisionFileDir, { encoding: 'utf8' })
        .filter(file => file.endsWith('.mobileprovision'));
    const profiles = files
        .map(file => {
        const fullPath = path_1.default.join(provisionFileDir, file);
        try {
            const mp = provision.readFromFile(fullPath);
            return {
                uuid: mp.UUID,
                name: mp.Name.split(':')[1] || mp.Name,
                teamName: mp.TeamName,
                filePath: fullPath,
            };
        }
        catch {
            return null;
        }
    })
        .filter((profile) => profile !== null);
    return { profiles };
}
//# sourceMappingURL=prepare-wda.js.map