/**
 * @fileoverview Android test runner for Capacitor-based mobile app
 * 
 * This script handles the build, installation, and testing of the Android app.
 * It ensures the app is properly synced, built, installed on a device/emulator,
 * and runs the test suite.
 * 
 * Process flow:
 * 1. Sync Capacitor project with latest web build
 * 2. Build debug APK
 * 3. Install APK on connected device/emulator
 * 4. Run instrumented tests
 * 
 * Prerequisites:
 * - Android SDK installed and ANDROID_HOME set
 * - Gradle installed and in PATH
 * - Connected Android device or running emulator
 * - Capacitor Android platform added to project
 * 
 * Exit codes:
 * - 0: Tests completed successfully
 * - 1: Build, installation, or test failure
 * 
 * @example
 * // Run directly
 * node scripts/test-android.js
 * 
 * // Run via npm script
 * npm run test:android
 * 
 * @requires child_process
 * @requires path
 * @requires readline
 * 
 * @author TimeSafari Team
 * @license MIT
 */

const { execSync } = require('child_process');
const { join } = require('path');
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync } = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));

// 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/android-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 connected Android devices
const checkConnectedDevices = async (log) => {
    log('๐Ÿ” Checking for Android devices...');
    const devices = execSync('adb devices').toString();
    const connectedDevices = devices.split('\n')
        .slice(1)
        .filter(line => line.includes('device'))
        .map(line => line.split('\t')[0])
        .filter(Boolean);

    if (connectedDevices.length === 0) {
        throw new Error('No Android devices or emulators connected. Please connect a device or start an emulator.');
    }

    log(`๐Ÿ“ฑ Found ${connectedDevices.length} device(s): ${connectedDevices.join(', ')}`);
    return connectedDevices;
};

// Verify Java installation
const verifyJavaInstallation = (log) => {
    log('๐Ÿ” Checking Java...');
    const javaHome = process.env.JAVA_HOME;
    if (!existsSync(javaHome)) {
        throw new Error(`Required Java not found at ${javaHome}. Please install OpenJDK.`);
    }
    log('โœ… Java found');
};

// Generate test data using generate_data.ts
const generateTestData = async (log) => {
    log('๐Ÿ”„ Generating test data...');
    
    // Create .generated directory if it doesn't exist
    if (!existsSync('.generated')) {
        mkdirSync('.generated', { recursive: true });
    }

    try {
        // Generate test data
        const testData = {
            CONTACT1_DID: "did:ethr:0x1943754837A09684Fd6380C1D80aa53E3F20E338",
            CLAIM_ID: "01JPVVX7FH0EKQWTQY9HTXZQDZ"
        };

        const claimDetails = {
            claim_id: "01JPVVX7FH0EKQWTQY9HTXZQDZ",
            issuedAt: "2025-03-21T08:07:57ZZ",
            issuer: "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"
        };

        const contacts = [
            {
                did: "did:ethr:0x1943754837A09684Fd6380C1D80aa53E3F20E338",
                name: "Test Contact"
            }
        ];

        // Write files
        log('๐Ÿ“ Writing test data files...');
        writeFileSync('.generated/test-env.json', JSON.stringify(testData, null, 2));
        writeFileSync('.generated/claim_details.json', JSON.stringify(claimDetails, null, 2));
        writeFileSync('.generated/contacts.json', JSON.stringify(contacts, null, 2));

        // Verify files were written
        log('โœ… Verifying test data files...');
        const files = [
            '.generated/test-env.json',
            '.generated/claim_details.json',
            '.generated/contacts.json'
        ];

        for (const file of files) {
            if (!existsSync(file)) {
                throw new Error(`Failed to create ${file}`);
            }
            log(`โœ… Created ${file}`);
        }

        log('โœ… Test data generated successfully');
    } catch (error) {
        log(`โŒ Failed to generate test data: ${error.message}`);
        throw error;
    }
};

// Parse shell environment file
const parseEnvFile = (filePath) => {
    const content = readFileSync(filePath, 'utf8');
    const env = {};
    content.split('\n').forEach(line => {
        const match = line.match(/^export\s+(\w+)="(.+)"$/);
        if (match) {
            env[match[1]] = match[2];
        }
    });
    return env;
};

// Run individual deeplink test
const executeDeeplink = async (url, description, log) => {
    log(`\n๐Ÿ”— Testing deeplink: ${description}`);
    log(`URL: ${url}`);
    
    try {
        // Stop the app before executing the deep link
        execSync('adb shell am force-stop app.timesafari');
        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1s
        
        execSync(`adb shell am start -W -a android.intent.action.VIEW -d "${url}" -c android.intent.category.BROWSABLE`);
        log(`โœ… Successfully executed: ${description}`);
        
        // Wait for app to load content
        await new Promise(resolve => setTimeout(resolve, 3000)); 
        
        // Wait for user confirmation before continuing
        await question('\nโŽ  Press Enter to continue to next test (or Ctrl+C to quit)...');
        
        // Press Back button to ensure app is in consistent state
        log(`๐Ÿ“ฑ Sending keystroke (BACK) to device...`);
        execSync('adb shell input keyevent KEYCODE_BACK');
        
        // Small delay after keystroke
        await new Promise(resolve => setTimeout(resolve, 2000));
    } catch (error) {
        log(`โŒ Failed to execute deeplink: ${description}`);
        log(`Error: ${error.message}`);
        throw error;
    }
};

// Run all deeplink tests
const runDeeplinkTests = async (log) => {
    log('๐Ÿ”— Starting deeplink tests...');
    
    try {
        // Load test data
        const testEnv = JSON.parse(readFileSync('.generated/test-env.json', 'utf8'));
        const claimDetails = JSON.parse(readFileSync('.generated/claim_details.json', 'utf8'));
        const contacts = JSON.parse(readFileSync('.generated/contacts.json', 'utf8'));

        // 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'
            }
        ];

        // Show test plan
        log('\n๐Ÿ“‹ Test Plan:');
        deeplinkTests.forEach((test, i) => {
            log(`${i + 1}. ${test.description}`);
        });

        // Execute each test
        let testsCompleted = 0;
        for (const test of deeplinkTests) {
            // Show progress
            log(`\n๐Ÿ“Š Progress: ${testsCompleted}/${deeplinkTests.length} tests completed`);
            
            // Show upcoming test info
            log('\n๐Ÿ“ฑ NEXT TEST:');
            log('------------------------');
            log(`Description: ${test.description}`);
            log(`URL: ${test.url}`);
            log('------------------------');

            await executeDeeplink(test.url, test.description, log);
            testsCompleted++;

            // If there are more tests, show the next one
            if (testsCompleted < deeplinkTests.length) {
                const nextTest = deeplinkTests[testsCompleted];
                log('\nโญ๏ธ  NEXT UP:');
                log('------------------------');
                log(`Next test will be: ${nextTest.description}`);
                log(`URL: ${nextTest.url}`);
                log('------------------------');
            }
        }

        log('\n๐ŸŽ‰ All deeplink tests completed successfully!');
        rl.close(); // Close readline interface when done
    } catch (error) {
        log('โŒ Deeplink tests failed');
        rl.close(); // Close readline interface on error
        throw error;
    }
};

// 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 Android project
const configureAndroidProject = async (log) => {
    log('๐Ÿ“ฑ Syncing Capacitor project...');
    execSync('npx cap sync android', { stdio: 'inherit' });
    log('โœ… Capacitor sync completed');

    log('โš™๏ธ Configuring Gradle properties...');
    const gradleProps = 'android/gradle.properties';
    
    // Create file if it doesn't exist
    if (!existsSync(gradleProps)) {
        execSync('touch android/gradle.properties');
    }

    // Check if line exists without using grep
    const gradleContent = readFileSync(gradleProps, 'utf8');
    if (!gradleContent.includes('android.suppressUnsupportedCompileSdk=34')) {
        execSync('echo "android.suppressUnsupportedCompileSdk=34" >> android/gradle.properties');
        log('โœ… Added SDK suppression to gradle.properties');
    } else {
        log('โœ… SDK suppression already configured in gradle.properties');
    }
};

// Build and test Android project
const buildAndTestAndroid = async (log, env) => {
    log('๐Ÿ—๏ธ Building Android project...');
    
    // Kill and restart ADB server first
    try {
        log('๐Ÿ”„ Restarting ADB server...');
        execSync('adb kill-server', { stdio: 'inherit' });
        await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s
        execSync('adb start-server', { stdio: 'inherit' });
        await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3s
        
        // Verify device connection
        const devices = execSync('adb devices').toString();
        if (!devices.includes('\tdevice')) {
            throw new Error('No devices connected after ADB restart');
        }
        log('โœ… ADB server restarted successfully');
    } catch (error) {
        log(`โš ๏ธ ADB restart failed: ${error.message}`);
        log('Continuing with build process...');
    }

    // Clean build
    log('๐Ÿงน Cleaning project...');
    execSync('cd android && ./gradlew clean', { stdio: 'inherit', env });
    log('โœ… Gradle clean completed');
    
    // Build
    log('๐Ÿ—๏ธ Building project...');
    execSync('cd android && ./gradlew build', { stdio: 'inherit', env });
    log('โœ… Gradle build completed');

    // Run tests with retry
    log('๐Ÿงช Running Android tests...');
    let retryCount = 0;
    const maxRetries = 3;

    while (retryCount < maxRetries) {
        try {
            // Verify ADB connection before tests
            execSync('adb devices', { stdio: 'inherit' });
            
            // Run the tests
            execSync('cd android && ./gradlew connectedAndroidTest', { 
                stdio: 'inherit', 
                env,
                timeout: 60000 // 1 minute timeout
            });
            log('โœ… Android tests completed');
            return;
        } catch (error) {
            retryCount++;
            log(`โš ๏ธ Test attempt ${retryCount} failed: ${error.message}`);
            
            if (retryCount < maxRetries) {
                log('๐Ÿ”„ Restarting ADB and retrying...');
                execSync('adb kill-server', { stdio: 'inherit' });
                await new Promise(resolve => setTimeout(resolve, 2000));
                execSync('adb start-server', { stdio: 'inherit' });
                await new Promise(resolve => setTimeout(resolve, 3000));
            } else {
                throw new Error(`Android tests failed after ${maxRetries} attempts`);
            }
        }
    }
};

// Run the app
const runAndroidApp = async (log, env) => {
    log('๐Ÿ“ฑ Running app on device...');
    execSync('npx cap run android', { stdio: 'inherit', env });
    log('โœ… App launched successfully');
};

/**
 * Runs the complete Android test suite including build, installation, and testing
 * 
 * The function performs the following steps:
 * 1. Checks for connected devices/emulators
 * 2. Ensures correct Java version is used
 * 3. Checks if app is already installed
 * 4. Syncs the Capacitor project with latest build
 * 5. Builds and runs instrumented Android tests
 * 
 * @async
 * @throws {Error} If any step in the build or test process fails
 * 
 * @example
 * runAndroidTests().catch(error => {
 *     console.error('Test execution failed:', error);
 *     process.exit(1);
 * });
 */
async function runAndroidTests() {
    // 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 Android build and test process...');

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

        await checkConnectedDevices(log);
        await verifyJavaInstallation(log);
        await buildWebAssets(log);
        await configureAndroidProject(log);
        const env = process.env;
        await buildAndTestAndroid(log, env);
        await runAndroidApp(log, env);

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

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

// Execute the test suite
runAndroidTests();

// Add cleanup handler for SIGINT
process.on('SIGINT', () => {
    rl.close();
    process.exit();
});