// SPDX-License-Identifier: Apache-2.0
import { expect } from 'chai';
import Sinon from 'sinon';
import { EventEmitter } from 'node:events';
import { KindExecutionException } from '../../../../../src/integration/kind/errors/kind-execution-exception.js';
import { KindParserException } from '../../../../../src/integration/kind/errors/kind-parser-exception.js';
// Create a mock implementation that mimics KindExecution behavior
class MockKindExecution {
    output = [];
    errOutput = [];
    exitCodeValue = null;
    mockProcess;
    constructor(command, workingDirectory, environmentVariables) {
        // eslint-disable-next-line unicorn/prefer-event-target
        this.mockProcess = new EventEmitter();
        // @ts-expect-error TS2339: Property stdout does not exist on type EventEmitter<DefaultEventMap>
        // eslint-disable-next-line unicorn/prefer-event-target
        this.mockProcess.stdout = new EventEmitter();
        // @ts-expect-error TS2339: Property stderr does not exist on type EventEmitter<DefaultEventMap>
        // eslint-disable-next-line unicorn/prefer-event-target
        this.mockProcess.stderr = new EventEmitter();
        // @ts-expect-error TS2339: Property stdin does not exist on type EventEmitter<DefaultEventMap>
        this.mockProcess.stdin = {
            end: Sinon.stub(),
        };
        // Listen for events from our mock process
        // @ts-expect-error TS2339: Property stdout does not exist on type EventEmitter<DefaultEventMap>
        this.mockProcess.stdout.on('data', data => this.output.push(data.toString()));
        // @ts-expect-error TS2339: Property stderr does not exist on type EventEmitter<DefaultEventMap>
        this.mockProcess.stderr.on('data', data => this.errOutput.push(data.toString()));
        this.mockProcess.on('exit', code => {
            this.exitCodeValue = code ?? 0;
        });
    }
    // Expose methods to control the mock process
    emitStdout(data) {
        // @ts-expect-error TS2339: Property stdout does not exist on type EventEmitter<DefaultEventMap>
        this.mockProcess.stdout.emit('data', data);
    }
    emitStderr(data) {
        // @ts-expect-error TS2339: Property stderr does not exist on type EventEmitter<DefaultEventMap>
        this.mockProcess.stderr.emit('data', data);
    }
    emitExit(code) {
        this.mockProcess.emit('exit', code);
    }
    emitError(error) {
        this.mockProcess.emit('error', error);
    }
    // Implement the methods being tested
    async waitForCompletion(timeout) {
        return new Promise((resolve, reject) => {
            if (timeout) {
                setTimeout(() => {
                    reject(new Error('Timed out waiting for the process to complete'));
                }, timeout);
            }
            this.mockProcess.on('exit', code => {
                if (code === 0) {
                    resolve();
                }
                else {
                    reject(new KindExecutionException(code, this.output.join(''), this.errOutput.join('')));
                }
            });
            this.mockProcess.on('error', error => {
                reject(error);
            });
        });
    }
    async responseAs(clazz) {
        try {
            await this.waitForCompletion();
            try {
                const constructor = clazz;
                return constructor.fromString(this.output.join(''));
            }
            catch (error) {
                throw new KindParserException(`Failed to deserialize the output into the specified class: ${clazz.name}`, error);
            }
        }
        catch (error) {
            if (error instanceof KindExecutionException || error instanceof KindParserException) {
                throw error;
            }
            throw new KindExecutionException(1, this.output.join(''), this.errOutput.join(''));
        }
    }
    async responseAsList(clazz) {
        try {
            await this.waitForCompletion();
            const lines = this.output
                .join('')
                .split('\n')
                .filter(line => line.trim().length > 0);
            const result = [];
            for (const line of lines) {
                try {
                    const constructor = clazz;
                    result.push(constructor.fromString(line));
                }
                catch (error) {
                    throw new KindParserException(`Failed to deserialize the output into a list of the specified class: ${clazz.name}`, error);
                }
            }
            return result;
        }
        catch (error) {
            if (error instanceof KindExecutionException || error instanceof KindParserException) {
                throw error;
            }
            throw new KindExecutionException(1, this.output.join(''), this.errOutput.join(''));
        }
    }
}
describe('KindExecution', () => {
    let execution;
    beforeEach(() => {
        // Create a test execution using our mock class
        execution = new MockKindExecution(['kind', 'create', 'cluster'], '/test/working/dir', { TEST_ENV: 'value' });
    });
    afterEach(() => {
        Sinon.restore();
    });
    describe('constructor', () => {
        it('should initialize the mock process correctly', () => {
            expect(execution).to.be.instanceOf(MockKindExecution);
        });
    });
    describe('waitForCompletion', () => {
        it('should resolve when process exits with code 0', async () => {
            const promise = execution.waitForCompletion();
            // Emit exit event with success code
            execution.emitExit(0);
            await expect(promise).to.be.fulfilled;
        });
        it('should reject when process exits with non-zero code', async () => {
            const promise = execution.waitForCompletion();
            // Emit stdout and stderr data
            execution.emitStdout('Some output');
            execution.emitStderr('Some error');
            // Emit exit event with error code
            execution.emitExit(1);
            await expect(promise).to.be.rejectedWith(KindExecutionException);
        });
        it('should reject when process has an error', async () => {
            const promise = execution.waitForCompletion();
            // Emit error event
            execution.emitError(new Error('Process error'));
            await expect(promise).to.be.rejectedWith(Error, 'Process error');
        });
        it('should timeout if the process takes too long', async () => {
            const clock = Sinon.useFakeTimers();
            const promise = execution.waitForCompletion(100); // 100ms timeout
            // Advance the timer beyond the timeout
            clock.tick(200);
            await expect(promise).to.be.rejectedWith(/Timed out/);
            clock.restore();
        });
    });
    describe('responseAs', () => {
        // Create a test class for responseAs testing
        class TestResponse {
            value = '';
            static fromString(string_) {
                const instance = new TestResponse();
                instance.value = string_.trim();
                return instance;
            }
        }
        it('should parse successful response into the specified class', async () => {
            const allPassing = await Promise.all([
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve, reject) => {
                    try {
                        // @ts-expect-error TS2345: Argument of type typeof TestResponse is not assignable to parameter of type
                        const result = await execution.responseAs(TestResponse);
                        expect(result).to.be.instanceOf(TestResponse);
                        expect(result.value).to.equal('test response');
                        resolve(true);
                    }
                    catch {
                        resolve(false);
                    }
                }),
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    execution.emitStdout('test response');
                    execution.emitExit(0);
                    resolve(true);
                }),
            ]);
            expect(allPassing).to.deep.equal([true, true]);
        });
        it('should reject if the process exits with error', async () => {
            const allPassing = await Promise.all([
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve, reject) => {
                    try {
                        // @ts-expect-error TS2345: Argument of type typeof TestResponse is not assignable to parameter of type
                        await execution.responseAs(TestResponse);
                        resolve(true);
                    }
                    catch (error) {
                        expect(error.name).to.be.equal(KindExecutionException.name);
                        resolve(false);
                    }
                }),
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    execution.emitStderr('error output');
                    execution.emitExit(1);
                    resolve(true);
                }),
            ]);
            expect(allPassing).to.deep.equal([false, true]);
        });
        it('should reject if parsing fails', async () => {
            // Setup a class that will throw during parsing
            class FailingClass {
                static fromString() {
                    throw new Error('Parsing error');
                }
            }
            const allPassing = await Promise.all([
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve, reject) => {
                    try {
                        // @ts-expect-error TS2345: Argument of type typeof FailingClass is not assignable to parameter of type
                        await execution.responseAs(FailingClass);
                        resolve(true);
                    }
                    catch (error) {
                        expect(error.name).to.be.equal(KindParserException.name);
                        resolve(false);
                    }
                }),
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    execution.emitStdout('some output');
                    execution.emitExit(0);
                    resolve(true);
                }),
            ]);
            expect(allPassing).to.deep.equal([false, true]);
        });
    });
    describe('responseAsList', () => {
        class TestItem {
            value = '';
            static fromString(string_) {
                const instance = new TestItem();
                instance.value = string_.trim();
                return instance;
            }
        }
        it('should parse successful response into a list of the specified class', async () => {
            const allPassing = await Promise.all([
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve, reject) => {
                    try {
                        // @ts-expect-error TS2345: Argument of type typeof TestItem is not assignable to parameter of type
                        const result = await execution.responseAsList(TestItem);
                        expect(result).to.be.an('array').with.lengthOf(3);
                        expect(result[0]).to.be.instanceOf(TestItem);
                        expect(result[0].value).to.equal('item1');
                        expect(result[1].value).to.equal('item2');
                        expect(result[2].value).to.equal('item3');
                        resolve(true);
                    }
                    catch {
                        resolve(false);
                    }
                }),
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    execution.emitStdout('item1\nitem2\nitem3');
                    execution.emitExit(0);
                    resolve(true);
                }),
            ]);
            expect(allPassing).to.deep.equal([true, true]);
        });
        it('should handle empty output', async () => {
            const allPassing = await Promise.all([
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve, reject) => {
                    try {
                        // @ts-expect-error TS2345: Argument of type typeof TestItem is not assignable to parameter of type
                        const result = await execution.responseAsList(TestItem);
                        expect(result).to.be.an('array').with.lengthOf(0);
                        resolve(true);
                    }
                    catch {
                        resolve(false);
                    }
                }),
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    execution.emitStdout('');
                    execution.emitExit(0);
                    resolve(true);
                }),
            ]);
            expect(allPassing).to.deep.equal([true, true]);
        });
        it('should reject if parsing fails', async () => {
            class FailingItem {
                static fromString() {
                    throw new Error('Parsing error');
                }
            }
            const allPassing = await Promise.all([
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve, reject) => {
                    try {
                        // @ts-expect-error TS2345: Argument of type typeof FailingItem is not assignable to parameter of type
                        const result = await execution.responseAsList(FailingItem);
                        expect(result).to.be.an('array').with.lengthOf(0);
                        resolve(true);
                    }
                    catch (error) {
                        expect(error.name).to.be.equal(KindParserException.name);
                        resolve(false);
                    }
                }),
                // eslint-disable-next-line no-async-promise-executor
                new Promise(async (resolve) => {
                    execution.emitStdout('item1\nitem2');
                    execution.emitExit(0);
                    resolve(true);
                }),
            ]);
            expect(allPassing).to.deep.equal([false, true]);
        });
    });
});
//# sourceMappingURL=kind-execution.test.js.map