/**
 * Base Test Class - Foundation for all test classes
 * Provides common test functionality, setup, and teardown
 */

const { getBrowserManager } = require('../browser/BrowserManager');
const { getAuthManager } = require('../authentication/AuthManager');
const path = require('path');
const fs = require('fs').promises;

class BaseTest {
    constructor(testName = 'UnnamedTest') {
        this.testName = testName;
        this.browserManager = getBrowserManager();
        this.authManager = getAuthManager();
        this.config = null;
        this.testResults = [];
        this.startTime = null;
        this.evidenceDir = path.join(process.cwd(), 'tests/evidence');
    }

    /**
     * Set up test environment
     * @param {Object} config - Test configuration
     * @returns {Promise<void>}
     */
    async setUp(config = {}) {
        this.config = {
            environment: 'staging',
            headless: true,
            slowMo: 0,
            timeout: 30000,
            screenshotOnFailure: true,
            ...config
        };

        this.startTime = Date.now();
        
        // Create evidence directories
        await this.createEvidenceDirectories();
        
        // Initialize browser
        await this.browserManager.initialize(this.config);
        
        // Set up auth manager
        await this.authManager.initialize(this.config);
        
        console.log(`🧪 Starting test: ${this.testName}`);
    }

    /**
     * Clean up test environment
     * @returns {Promise<void>}
     */
    async tearDown() {
        const endTime = Date.now();
        const duration = endTime - this.startTime;
        
        try {
            // Generate test report
            await this.generateTestReport(duration);
            
            // Clean up authentication
            await this.authManager.cleanup();
            
            // Clean up browser
            await this.browserManager.cleanup();
            
            console.log(`✅ Completed test: ${this.testName} (${duration}ms)`);
        } catch (error) {
            console.error(`❌ Error during teardown: ${error.message}`);
        }
    }

    /**
     * Run a test step with error handling and reporting
     * @param {string} stepName - Name of the test step
     * @param {Function} testFunction - Test function to execute
     * @param {Object} options - Test options
     * @returns {Promise<*>} Test result
     */
    async runTestStep(stepName, testFunction, options = {}) {
        const stepStartTime = Date.now();
        const stepConfig = {
            screenshotOnFailure: true,
            continueOnFailure: false,
            ...options
        };

        console.log(`  📋 ${stepName}`);

        try {
            const result = await testFunction();
            
            const stepEndTime = Date.now();
            const stepDuration = stepEndTime - stepStartTime;
            
            this.testResults.push({
                step: stepName,
                status: 'passed',
                duration: stepDuration,
                result: result
            });
            
            console.log(`    ✅ Passed (${stepDuration}ms)`);
            return result;
            
        } catch (error) {
            const stepEndTime = Date.now();
            const stepDuration = stepEndTime - stepStartTime;
            
            // Take screenshot on failure if enabled
            if (stepConfig.screenshotOnFailure) {
                await this.takeFailureScreenshot(stepName, error);
            }
            
            this.testResults.push({
                step: stepName,
                status: 'failed',
                duration: stepDuration,
                error: error.message,
                stack: error.stack
            });
            
            console.error(`    ❌ Failed (${stepDuration}ms): ${error.message}`);
            
            if (!stepConfig.continueOnFailure) {
                throw error;
            }
            
            return null;
        }
    }

    /**
     * Take screenshot on test failure
     * @param {string} stepName - Name of failed step
     * @param {Error} error - Error that occurred
     * @returns {Promise<string|null>} Path to screenshot or null
     */
    async takeFailureScreenshot(stepName, error) {
        try {
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const filename = `${this.testName}-${stepName}-failure-${timestamp}.png`;
            
            const screenshotPath = await this.browserManager.takeScreenshot(filename);
            console.log(`    📸 Screenshot saved: ${filename}`);
            
            return screenshotPath;
        } catch (screenshotError) {
            console.warn(`Failed to take failure screenshot: ${screenshotError.message}`);
            return null;
        }
    }

    /**
     * Assert condition with custom message
     * @param {boolean} condition - Condition to assert
     * @param {string} message - Error message if assertion fails
     * @param {*} expected - Expected value
     * @param {*} actual - Actual value
     * @returns {void}
     */
    assert(condition, message, expected = null, actual = null) {
        if (!condition) {
            let errorMessage = message;
            if (expected !== null && actual !== null) {
                errorMessage += ` Expected: ${expected}, Actual: ${actual}`;
            }
            throw new Error(errorMessage);
        }
    }

    /**
     * Assert that two values are equal
     * @param {*} actual - Actual value
     * @param {*} expected - Expected value
     * @param {string} message - Custom error message
     * @returns {void}
     */
    assertEqual(actual, expected, message = '') {
        const defaultMessage = `Values are not equal.`;
        this.assert(
            actual === expected,
            message || defaultMessage,
            expected,
            actual
        );
    }

    /**
     * Assert that value is truthy
     * @param {*} value - Value to check
     * @param {string} message - Custom error message
     * @returns {void}
     */
    assertTrue(value, message = '') {
        const defaultMessage = `Value is not truthy.`;
        this.assert(!!value, message || defaultMessage, true, !!value);
    }

    /**
     * Assert that value is falsy
     * @param {*} value - Value to check
     * @param {string} message - Custom error message
     * @returns {void}
     */
    assertFalse(value, message = '') {
        const defaultMessage = `Value is not falsy.`;
        this.assert(!value, message || defaultMessage, false, !!value);
    }

    /**
     * Assert that element exists on page
     * @param {string} selector - CSS selector
     * @param {string} message - Custom error message
     * @returns {Promise<void>}
     */
    async assertElementExists(selector, message = '') {
        const page = this.browserManager.getCurrentPage();
        const exists = await page.$(selector) !== null;
        const defaultMessage = `Element not found: ${selector}`;
        this.assert(exists, message || defaultMessage);
    }

    /**
     * Assert that element is visible
     * @param {string} selector - CSS selector
     * @param {string} message - Custom error message
     * @returns {Promise<void>}
     */
    async assertElementVisible(selector, message = '') {
        const page = this.browserManager.getCurrentPage();
        const visible = await page.isVisible(selector);
        const defaultMessage = `Element not visible: ${selector}`;
        this.assert(visible, message || defaultMessage);
    }

    /**
     * Assert that element contains text
     * @param {string} selector - CSS selector
     * @param {string} expectedText - Expected text
     * @param {string} message - Custom error message
     * @returns {Promise<void>}
     */
    async assertElementText(selector, expectedText, message = '') {
        const page = this.browserManager.getCurrentPage();
        const actualText = await page.textContent(selector);
        const defaultMessage = `Element text mismatch for ${selector}`;
        this.assert(
            actualText && actualText.includes(expectedText),
            message || defaultMessage,
            expectedText,
            actualText
        );
    }

    /**
     * Assert that URL matches pattern
     * @param {RegExp|string} pattern - URL pattern
     * @param {string} message - Custom error message
     * @returns {void}
     */
    assertUrlMatches(pattern, message = '') {
        const page = this.browserManager.getCurrentPage();
        const currentUrl = page.url();
        
        const matches = pattern instanceof RegExp 
            ? pattern.test(currentUrl)
            : currentUrl.includes(pattern);
            
        const defaultMessage = `URL does not match pattern. Pattern: ${pattern}, URL: ${currentUrl}`;
        this.assert(matches, message || defaultMessage, pattern, currentUrl);
    }

    /**
     * Wait with timeout
     * @param {number} ms - Milliseconds to wait
     * @returns {Promise<void>}
     */
    async wait(ms) {
        await new Promise(resolve => setTimeout(resolve, ms));
    }

    /**
     * Create evidence directories
     * @returns {Promise<void>}
     * @private
     */
    async createEvidenceDirectories() {
        const dirs = [
            path.join(this.evidenceDir, 'screenshots'),
            path.join(this.evidenceDir, 'videos'),
            path.join(this.evidenceDir, 'reports'),
            path.join(this.evidenceDir, 'logs')
        ];

        for (const dir of dirs) {
            try {
                await fs.mkdir(dir, { recursive: true });
            } catch (error) {
                console.warn(`Failed to create directory ${dir}: ${error.message}`);
            }
        }
    }

    /**
     * Generate test report
     * @param {number} totalDuration - Total test duration
     * @returns {Promise<void>}
     * @private
     */
    async generateTestReport(totalDuration) {
        const report = {
            testName: this.testName,
            startTime: this.startTime,
            endTime: Date.now(),
            totalDuration: totalDuration,
            config: this.config,
            results: this.testResults,
            summary: this.getTestSummary()
        };

        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const reportPath = path.join(
            this.evidenceDir, 
            'reports', 
            `${this.testName}-${timestamp}.json`
        );

        try {
            await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
            console.log(`📊 Test report saved: ${reportPath}`);
        } catch (error) {
            console.warn(`Failed to save test report: ${error.message}`);
        }
    }

    /**
     * Get test summary statistics
     * @returns {Object} Test summary
     * @private
     */
    getTestSummary() {
        const passed = this.testResults.filter(r => r.status === 'passed').length;
        const failed = this.testResults.filter(r => r.status === 'failed').length;
        const total = this.testResults.length;

        return {
            total: total,
            passed: passed,
            failed: failed,
            successRate: total > 0 ? ((passed / total) * 100).toFixed(2) : '0'
        };
    }
}

module.exports = BaseTest;