/**
 * @fileoverview iOS test runner for Capacitor-based mobile app
 * 
 * This script handles the build and testing of the iOS app using Xcode's
 * command-line tools. It ensures the app is properly synced with the latest
 * web build and runs the test suite on a specified iOS simulator.
 * 
 * Process flow:
 * 1. Sync Capacitor project with latest web build
 * 2. Build app for iOS simulator
 * 3. Run XCTest suite
 * 
 * Prerequisites:
 * - macOS operating system
 * - Xcode installed with command line tools
 * - iOS simulator available
 * - Capacitor iOS platform added to project
 * - Valid iOS development certificates
 * 
 * Exit codes:
 * - 0: Tests completed successfully
 * - 1: Build or test failure
 * 
 * @example
 * // Run directly
 * node scripts/test-ios.js
 * 
 * // Run via npm script
 * npm run test:ios
 * 
 * @requires child_process
 * @requires path
 * @requires fs
 * 
 * @author TimeSafari Team
 * @license MIT
 */

const { execSync } = require('child_process');
const { join } = require('path');
const { existsSync, mkdirSync, appendFileSync, readFileSync } = require('fs');

// Format date as YYYY-MM-DD-HHMMSS
const getLogFileName = () => {
    const now = new Date();
    const date = now.toISOString().split('T')[0];
    const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
    return `build_logs/ios-build-${date}-${time}.log`;
};

// Create logger function
const createLogger = (logFile) => {
    return (message) => {
        const timestamp = new Date().toISOString();
        const logMessage = `[${timestamp}] ${message}\n`;
        console.log(message);
        appendFileSync(logFile, logMessage);
    };
};

// Check for iOS simulator
const checkSimulator = async (log) => {
    log('๐Ÿ” Checking for iOS simulator...');
    const simulatorsOutput = execSync('xcrun simctl list devices available -j').toString();
    const simulatorsData = JSON.parse(simulatorsOutput);
    
    // Get all available devices/simulators with their UDIDs
    const allDevices = [];
    
    // Process all runtime groups (iOS versions)
    Object.entries(simulatorsData.devices).forEach(([runtime, devices]) => {
        devices.forEach(device => {
            allDevices.push({
                name: device.name,
                udid: device.udid,
                state: device.state,
                runtime: runtime,
                isIphone: device.name.includes('iPhone'),
            });
        });
    });
    
    // Check for booted simulators first
    const bootedDevices = allDevices.filter(device => device.state === 'Booted');
    
    if (bootedDevices.length > 0) {
        log(`๐Ÿ“ฑ Found ${bootedDevices.length} running simulator(s): ${bootedDevices.map(d => d.name).join(', ')}`);
        return bootedDevices;
    }
    
    // No booted devices found, try to boot one
    log('โš ๏ธ No running iOS simulator found. Attempting to boot one...');
    
    // Prefer iPhone devices, especially newer models
    const preferredDevices = [
        'iPhone 15', 'iPhone 14', 'iPhone 13', 'iPhone 12', 'iPhone',  // Prefer newer iPhones first
        'iPad'  // Then iPads if no iPhones available
    ];
    
    let deviceToLaunch = null;
    
    // Try to find a device from our preferred list
    for (const preferredName of preferredDevices) {
        const matchingDevices = allDevices.filter(device => 
            device.name.includes(preferredName) && device.state === 'Shutdown');
        
        if (matchingDevices.length > 0) {
            // Sort by runtime to prefer newer iOS versions
            matchingDevices.sort((a, b) => b.runtime.localeCompare(a.runtime));
            deviceToLaunch = matchingDevices[0];
            break;
        }
    }
    
    // If no preferred device found, take any available device
    if (!deviceToLaunch && allDevices.length > 0) {
        const availableDevices = allDevices.filter(device => device.state === 'Shutdown');
        if (availableDevices.length > 0) {
            deviceToLaunch = availableDevices[0];
        }
    }
    
    if (!deviceToLaunch) {
        throw new Error('No available iOS simulators found. Please create a simulator in Xcode first.');
    }
    
    // Boot the selected simulator
    log(`๐Ÿš€ Booting iOS simulator: ${deviceToLaunch.name} (${deviceToLaunch.runtime})`);
    execSync(`xcrun simctl boot ${deviceToLaunch.udid}`);
    
    // Wait for simulator to fully boot
    log('โณ Waiting for simulator to boot completely...');
    // Give the simulator time to fully boot before proceeding
    await new Promise(resolve => setTimeout(resolve, 10000));
    
    log(`โœ… Successfully booted simulator: ${deviceToLaunch.name}`);
    
    return [{ name: deviceToLaunch.name, udid: deviceToLaunch.udid }];
};

// Verify Xcode installation
const verifyXcodeInstallation = (log) => {
    log('๐Ÿ” Checking Xcode installation...');
    try {
        execSync('xcode-select -p');
        log('โœ… Xcode command line tools found');
    } catch (error) {
        throw new Error('Xcode command line tools not found. Please install Xcode first.');
    }
};

// Generate test data using generate_data.ts
const generateTestData = async (log) => {
    log('๐Ÿ”„ Generating test data...');
    
    // Check if test-scripts directory exists
    if (!existsSync('test-scripts')) {
        log('โš ๏ธ test-scripts directory not found');
        log('โš ๏ธ Current directory: ' + process.cwd());
        
        // List directories to help debug
        const { readdirSync } = require('fs');
        log('๐Ÿ“‚ Directories in current path:');
        try {
            const files = readdirSync('.');
            files.forEach(file => {
                const isDir = existsSync(file) && require('fs').statSync(file).isDirectory();
                log(`${isDir ? '๐Ÿ“' : '๐Ÿ“„'} ${file}`);
            });
        } catch (err) {
            log(`โš ๏ธ Error listing directory: ${err.message}`);
        }
    } else {
        log('โœ… Found test-scripts directory');
        
        // Check if generate_data.ts exists
        if (existsSync('test-scripts/generate_data.ts')) {
            log('โœ… Found generate_data.ts');
        } else {
            log('โš ๏ธ generate_data.ts not found in test-scripts directory');
            
            // List files in test-scripts to help debug
            const { readdirSync } = require('fs');
            log('๐Ÿ“‚ Files in test-scripts:');
            try {
                const files = readdirSync('test-scripts');
                files.forEach(file => {
                    log(`๐Ÿ“„ ${file}`);
                });
            } catch (err) {
                log(`โš ๏ธ Error listing test-scripts: ${err.message}`);
            }
        }
    }
    
    // Create .generated directory if it doesn't exist
    if (!existsSync('.generated')) {
        log('๐Ÿ“ Creating .generated directory');
        mkdirSync('.generated', { recursive: true });
    }
    
    try {
        // Try to generate test data using the script
        log('๐Ÿ”„ Running test data generation script...');
        execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
        log('โœ… Test data generation script completed');
        
        // Verify the generated files exist
        const requiredFiles = [
            '.generated/test-env.json',
            '.generated/claim_details.json',
            '.generated/contacts.json'
        ];
        
        log('๐Ÿ” Verifying generated files:');
        for (const file of requiredFiles) {
            if (!existsSync(file)) {
                log(`โš ๏ธ Required file ${file} was not generated`);
                throw new Error(`Required file ${file} was not generated`);
            } else {
                log(`โœ… ${file} exists`);
            }
        }
    } catch (error) {
        log(`โš ๏ธ Failed to generate test data: ${error.message}`);
        log('โš ๏ธ Creating fallback test data...');
        
        // Create minimal fallback test data
        const fallbackTestEnv = {
            "CONTACT1_DID": "did:example:123456789",
            "APP_URL": "https://app.timesafari.example"
        };
        
        const fallbackClaimDetails = {
            "claim_id": "claim_12345",
            "title": "Test Claim",
            "description": "This is a test claim"
        };
        
        const fallbackContacts = [
            {
                "id": "contact1",
                "name": "Test Contact",
                "did": "did:example:123456789"
            }
        ];
        
        // Use writeFileSync to overwrite any existing files
        const { writeFileSync } = require('fs');
        writeFileSync('.generated/test-env.json', JSON.stringify(fallbackTestEnv, null, 2));
        writeFileSync('.generated/claim_details.json', JSON.stringify(fallbackClaimDetails, null, 2));
        writeFileSync('.generated/contacts.json', JSON.stringify(fallbackContacts, null, 2));
        
        log('โœ… Fallback test data created');
        
        // Verify files were created
        const requiredFiles = [
            '.generated/test-env.json',
            '.generated/claim_details.json',
            '.generated/contacts.json'
        ];
        
        log('๐Ÿ” Verifying fallback files:');
        for (const file of requiredFiles) {
            if (!existsSync(file)) {
                log(`โš ๏ธ Failed to create ${file}`);
            } else {
                log(`โœ… Created ${file}`);
            }
        }
    }
};

// Build web assets
const buildWebAssets = async (log) => {
    log('๐ŸŒ Building web assets...');
    execSync('rm -rf dist', { stdio: 'inherit' });
    execSync('npm run build:web', { stdio: 'inherit' });
    execSync('npm run build:capacitor', { stdio: 'inherit' });
    log('โœ… Web assets built successfully');
};

// Configure iOS project
const configureIosProject = async (log) => {
    log('๐Ÿ“ฑ Syncing Capacitor project...');
    try {
        execSync('npx cap sync ios', { stdio: 'inherit' });
        log('โœ… Capacitor sync completed');
    } catch (error) {
        log('โš ๏ธ Capacitor sync encountered issues. Attempting to continue...');
    }

    // Register URL scheme for deeplink tests
    log('๐Ÿ”— Configuring URL scheme for deeplink tests...');
    if (checkAndRegisterUrlScheme(log)) {
        log('โœ… URL scheme configuration completed');
    }

    log('โš™๏ธ Installing CocoaPods dependencies...');
    try {
        // Try to run pod install normally first
        execSync('cd ios/App && pod install', { stdio: 'inherit' });
    } catch (error) {
        // If that fails, try using sudo (requires password)
        log('โš ๏ธ CocoaPods installation failed. Trying with sudo...');
        try {
            execSync('cd ios/App && sudo pod install', { stdio: 'inherit' });
        } catch (sudoError) {
            // If both methods fail, alert the user
            log('โŒ CocoaPods installation failed.');
            log('Please run one of the following commands manually:');
            log('1. cd ios/App && pod install');
            log('2. cd ios/App && sudo pod install');
            log('3. Install CocoaPods through Homebrew: brew install cocoapods');
            throw new Error('CocoaPods installation failed. See log for details.');
        }
    }
    log('โœ… CocoaPods installation completed');
};

// Build and test iOS project
const buildAndTestIos = async (log, simulator) => {
    const simulatorName = simulator[0].name;
    log('๐Ÿ—๏ธ Building iOS project...');
    execSync('cd ios/App && xcodebuild clean -workspace App.xcworkspace -scheme App', { stdio: 'inherit' });
    log('โœ… Xcode clean completed');
    
    log(`๐Ÿ—๏ธ Building for simulator: ${simulatorName}`);
    execSync(`cd ios/App && xcodebuild build -workspace App.xcworkspace -scheme App -destination "platform=iOS Simulator,name=${simulatorName}"`, { stdio: 'inherit' });
    log('โœ… Xcode build completed');

    // Check if the project is configured for testing by querying the scheme capabilities
    try {
        log(`๐Ÿงช Checking if scheme is configured for testing`);
        const schemeInfo = execSync(`cd ios/App && xcodebuild -scheme App -showBuildSettings | grep TEST`).toString();
        
        if (schemeInfo.includes('ENABLE_TESTABILITY = YES')) {
            log(`๐Ÿงช Attempting to run tests on simulator: ${simulatorName}`);
            try {
                execSync(`cd ios/App && xcodebuild test -workspace App.xcworkspace -scheme App -destination "platform=iOS Simulator,name=${simulatorName}"`, { stdio: 'inherit' });
                log('โœ… iOS tests completed successfully');
            } catch (testError) {
                log(`โš ๏ธ Tests failed or scheme not properly configured for testing: ${testError.message}`);
                log('โš ๏ธ This is normal if no test targets have been added to the project');
                log('โš ๏ธ Skipping test step and continuing with the app launch');
            }
        } else {
            log('โš ๏ธ Project does not have testing enabled in build settings');
            log('โš ๏ธ Skipping test step and continuing with the app launch');
        }
    } catch (error) {
        log('โš ๏ธ Unable to determine if testing is configured');
        log('โš ๏ธ Skipping test step and continuing with the app launch');
    }
};

// Run the app
const runIosApp = async (log, simulator) => {
    const simulatorName = simulator[0].name;
    const simulatorUdid = simulator[0].udid;
    
    log(`๐Ÿ“ฑ Running app in simulator: ${simulatorName} (${simulatorUdid})...`);
    // Use the --target parameter to specify the device directly, avoiding the UI prompt
    execSync(`npx cap run ios --target="${simulatorUdid}"`, { stdio: 'inherit' });
    log('โœ… App launched successfully');
};

/**
 * Run deeplink tests 
 * Optionally tests deeplinks if the test data is available
 * 
 * @param {function} log - Logging function
 * @returns {Promise<void>}
 */
const runDeeplinkTests = async (log) => {
    log('๐Ÿ”— Starting deeplink tests...');
    
    // Register URL scheme if needed
    checkAndRegisterUrlScheme(log);
    
    // Check if test data files exist first
    const requiredFiles = [
        '.generated/test-env.json',
        '.generated/claim_details.json',
        '.generated/contacts.json'
    ];
    
    for (const file of requiredFiles) {
        if (!existsSync(file)) {
            log(`โš ๏ธ Required file ${file} does not exist`);
            log('โš ๏ธ Skipping deeplink tests');
            return;
        }
    }
    
    try {
        // Load test data
        log('๐Ÿ“‚ Loading test data from .generated directory');
        let testEnv, claimDetails, contacts;
        
        try {
            const testEnvContent = readFileSync('.generated/test-env.json', 'utf8');
            testEnv = JSON.parse(testEnvContent);
            log('โœ… Loaded test-env.json');
        } catch (error) {
            log(`โš ๏ธ Failed to load test-env.json: ${error.message}`);
            return;
        }
        
        try {
            const claimDetailsContent = readFileSync('.generated/claim_details.json', 'utf8');
            claimDetails = JSON.parse(claimDetailsContent);
            log('โœ… Loaded claim_details.json');
        } catch (error) {
            log(`โš ๏ธ Failed to load claim_details.json: ${error.message}`);
            return;
        }
        
        try {
            const contactsContent = readFileSync('.generated/contacts.json', 'utf8');
            contacts = JSON.parse(contactsContent);
            log('โœ… Loaded contacts.json');
        } catch (error) {
            log(`โš ๏ธ Failed to load contacts.json: ${error.message}`);
            return;
        }

        // Check if the app URL scheme is registered in the simulator
        log('๐Ÿ” Checking if URL scheme is registered in simulator...');
        try {
            // Attempt to open a simple URL with the scheme
            execSync(`xcrun simctl openurl booted "timesafari://test"`, { stdio: 'pipe' });
            log('โœ… URL scheme is registered and working');
        } catch (error) {
            const errorMessage = error.message || '';
            
            // Check for the specific error code that indicates an unregistered URL scheme
            if (errorMessage.includes('OSStatus error -10814') || errorMessage.includes('NSOSStatusErrorDomain, code=-10814')) {
                log('โš ๏ธ URL scheme "timesafari://" is not registered in the app or app is not running');
                log('โš ๏ธ The scheme was added to Info.plist but the app may need to be rebuilt');
                log('โš ๏ธ Trying to continue with tests, but they may fail');
            }
        }

        // Test URLs
        const deeplinkTests = [
            {
                url: `timesafari://claim/${claimDetails.claim_id}`,
                description: 'Claim view'
            },
            {
                url: `timesafari://claim-cert/${claimDetails.claim_id}`,
                description: 'Claim certificate view'
            },
            {
                url: `timesafari://claim-add-raw/${claimDetails.claim_id}`,
                description: 'Raw claim addition'
            },
            {
                url: 'timesafari://did/test',
                description: 'DID view with test identifier'
            },
            {
                url: `timesafari://did/${testEnv.CONTACT1_DID}`,
                description: 'DID view with contact DID'
            },
            {
                url: `timesafari://contact-edit/${testEnv.CONTACT1_DID}`,
                description: 'Contact editing'
            },
            {
                url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
                description: 'Contacts import'
            }
        ];

        // Execute each test
        let testsCompleted = 0;
        let testsSkipped = 0;
        
        for (const test of deeplinkTests) {
            try {
                log(`\n๐Ÿ”— Testing deeplink: ${test.description}`);
                log(`URL: ${test.url}`);
                
                execSync(`xcrun simctl openurl booted "${test.url}"`, { stdio: 'pipe' });
                log(`โœ… Successfully executed: ${test.description}`);
                testsCompleted++;
                
                // Wait between tests
                await new Promise(resolve => setTimeout(resolve, 5000));
            } catch (deeplinkError) {
                const errorMessage = deeplinkError.message || '';
                
                // Handle specific error for URL scheme not registered
                if (errorMessage.includes('OSStatus error -10814') || errorMessage.includes('NSOSStatusErrorDomain, code=-10814')) {
                    log(`โš ๏ธ URL scheme not properly handled: ${test.description}`);
                    testsSkipped++;
                } else {
                    log(`โš ๏ธ Failed to execute deeplink test: ${test.description}`);
                    log(`โš ๏ธ Error: ${errorMessage}`);
                }
                log('โš ๏ธ Continuing with next test...');
            }
        }

        log(`โœ… Deeplink tests completed: ${testsCompleted} successful, ${testsSkipped} skipped`);
        
        if (testsSkipped > 0) {
            log('\n๐Ÿ“ Note about skipped tests:');
            log('1. The app needs to have the URL scheme registered in Info.plist');
            log('2. The app needs to be rebuilt after registering the URL scheme');
            log('3. The app must be running in the foreground for deeplink tests to work');
            log('4. If these conditions are met and tests still fail, check URL handling in the app code');
        }
    } catch (error) {
        log(`โŒ Deeplink tests setup failed: ${error.message}`);
        log('โš ๏ธ Deeplink tests might be unavailable or test data is missing');
        // Don't rethrow the error to prevent halting the process
    }
};

// Check and register URL scheme if needed
const checkAndRegisterUrlScheme = (log) => {
    log('๐Ÿ” Checking if URL scheme is registered in Info.plist...');
    
    const infoPlistPath = 'ios/App/App/Info.plist';
    
    // Check if Info.plist exists
    if (!existsSync(infoPlistPath)) {
        log('โš ๏ธ Info.plist not found at: ' + infoPlistPath);
        return false;
    }
    
    // Read Info.plist content
    const infoPlistContent = readFileSync(infoPlistPath, 'utf8');
    
    // Check if URL scheme is already registered
    if (infoPlistContent.includes('<string>timesafari</string>')) {
        log('โœ… URL scheme "timesafari://" is already registered in Info.plist');
        return true;
    }
    
    log('โš ๏ธ URL scheme "timesafari://" is not registered in Info.plist');
    log('โš ๏ธ Attempting to register the URL scheme automatically...');
    
    try {
        // Look for the closing dict tag to insert our URL types
        const closingDictIndex = infoPlistContent.lastIndexOf('</dict>');
        if (closingDictIndex === -1) {
            log('โš ๏ธ Could not find closing dict tag in Info.plist');
            return false;
        }
        
        // Create URL types entry
        const urlTypesEntry = `
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLName</key>
			<string>app.timesafari.app</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>timesafari</string>
			</array>
		</dict>
	</array>`;
        
        // Insert URL types entry before closing dict
        const updatedPlistContent = 
            infoPlistContent.substring(0, closingDictIndex) + 
            urlTypesEntry + 
            infoPlistContent.substring(closingDictIndex);
        
        // Write updated content back to Info.plist
        const { writeFileSync } = require('fs');
        writeFileSync(infoPlistPath, updatedPlistContent, 'utf8');
        
        log('โœ… URL scheme "timesafari://" registered in Info.plist');
        log('โš ๏ธ You will need to rebuild the app for changes to take effect');
        return true;
    } catch (error) {
        log(`โš ๏ธ Failed to register URL scheme: ${error.message}`);
        return false;
    }
};

/**
 * Runs the complete iOS test suite including build and testing
 * 
 * The function performs the following steps:
 * 1. Syncs the Capacitor project with latest web build
 * 2. Builds the app using xcodebuild
 * 3. Optionally runs tests if configured
 * 4. Launches the app in the simulator
 * 
 * If no simulator is running, it automatically selects and boots one.
 * 
 * @async
 * @throws {Error} If any step in the build process fails
 * 
 * @example
 * runIosTests().catch(error => {
 *     console.error('Test execution failed:', error);
 *     process.exit(1);
 * });
 */
async function runIosTests() {
    // Create build_logs directory if it doesn't exist
    if (!existsSync('build_logs')) {
        mkdirSync('build_logs');
    }

    const logFile = getLogFileName();
    const log = createLogger(logFile);

    try {
        log('๐Ÿš€ Starting iOS build and test process...');

        // Generate test data first
        await generateTestData(log);

        // Verify Xcode installation
        verifyXcodeInstallation(log);
        
        // Check for simulator or boot one if needed
        const simulator = await checkSimulator(log);
        
        // Build web assets and configure iOS project
        await buildWebAssets(log);
        await configureIosProject(log);
        
        // Build and test using the selected simulator
        await buildAndTestIos(log, simulator);
        
        // Run the app in the simulator
        await runIosApp(log, simulator);

        // Run deeplink tests after app is installed
        await runDeeplinkTests(log);

        log('๐ŸŽ‰ iOS build and test process completed successfully');
        log(`๐Ÿ“ Full build log available at: ${logFile}`);
    } catch (error) {
        log(`โŒ iOS tests failed: ${error.message}`);
        log(`๐Ÿ“ Check build log for details: ${logFile}`);
        process.exit(1);
    }
}

// Execute the test suite
runIosTests();