{%- import "cloudevents.jinja.include" as cloudEvents -%}
{%- import "util.jinja.include" as util -%}
// This code was generated by the xRegistry tool.
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.

import { ServiceBusClient, ServiceBusSender } from '@azure/service-bus';
import { GenericContainer, StartedTestContainer, StartedNetwork, Network, Wait } from 'testcontainers';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
{%- set messagegroups = root.messagegroups %}
{%- set uses_cloudevents_message = cloudEvents.usesCloudEvents(root) %}

import * as {{ data_project_name }} from '../../{{ data_project_name }}/dist/index.js';
import { ServiceBusProcessor, {% for messagegroupid, messagegroup in messagegroups.items() -%}{%- set class_name = (messagegroupid | pascal | strip_namespace) + "EventDispatcher" %}{{ class_name }}{% if not loop.last %}, {% endif %}{% endfor %} } from '../src';
{%- if uses_cloudevents_message %}
import { CloudEvent, HTTP } from 'cloudevents';
{%- endif %}

// Skip tests in CI unless explicitly enabled due to Service Bus emulator 
// requiring 150-240+ seconds to start. Wait strategy uses log message 
// "Emulator Service is Successfully Up!" which appears when port 5672 becomes accessible.
const SKIP_SERVICEBUS_TESTS = process.env.SKIP_SERVICEBUS_TESTS === 'true';
const describeTest = SKIP_SERVICEBUS_TESTS ? describe.skip : describe;

const QUEUE_NAME = 'myqueue';

jest.setTimeout(360000); // 6 minutes for container startup and test execution

{%- for messagegroupid, messagegroup in messagegroups.items() %}
{%- set pascal_group_name = messagegroupid | pascal %}
{%- set class_name = (pascal_group_name | strip_namespace) + "EventDispatcher" %}
{%- set test_class_name = (project_name | strip_dots | pascal) + (pascal_group_name | strip_dots) + "Tests" %}

describeTest('{{ test_class_name }}', () => {
    let network: StartedNetwork;
    let sqlEdgeContainer: StartedTestContainer;
    let sbEmulatorContainer: StartedTestContainer;
    let client: ServiceBusClient;
    let sender: ServiceBusSender;
    let configFilePath: string;
    
    beforeAll(async () => {
        // Create emulator configuration file
        const emulatorConfig = {
            UserConfig: {
                Namespaces: [
                    {
                        Name: 'sbemulatorns',
                        Queues: [
                            {
                                Name: QUEUE_NAME,
                                Properties: {
                                    DeadLetteringOnMessageExpiration: false,
                                    DefaultMessageTimeToLive: 'PT1H',
                                    LockDuration: 'PT1M',
                                    MaxDeliveryCount: 10,
                                    RequiresDuplicateDetection: false,
                                    RequiresSession: false
                                }
                            }
                        ]
                    }
                ],
                Logging: {
                    Type: 'File'
                }
            }
        };

        configFilePath = path.join(os.tmpdir(), `sb-config-${Date.now()}.json`);
        fs.writeFileSync(configFilePath, JSON.stringify(emulatorConfig, null, 2));

        // Create network
        network = await new Network().start();

        // Start SQL Edge container
        sqlEdgeContainer = await new GenericContainer('mcr.microsoft.com/azure-sql-edge:latest')
            .withEnvironment({
                'ACCEPT_EULA': 'Y',
                'MSSQL_SA_PASSWORD': 'StrongPassword!1'
            })
            .withNetwork(network)
            .withNetworkAliases('sqledge')
            .withExposedPorts(1433)
            .withWaitStrategy(Wait.forListeningPorts())
            .withStartupTimeout(120000)
            .start();

        // Start Service Bus emulator container
        // Emulator startup takes 150-240+ seconds; wait for log message indicating readiness
        sbEmulatorContainer = await new GenericContainer('mcr.microsoft.com/azure-messaging/servicebus-emulator:latest')
            .withBindMounts([{
                source: configFilePath,
                target: '/ServiceBus_Emulator/ConfigFiles/Config.json'
            }])
            .withExposedPorts(5672)
            .withNetwork(network)
            .withNetworkAliases('sb-emulator')
            .withEnvironment({
                'SQL_SERVER': 'sqledge',
                'MSSQL_SA_PASSWORD': 'StrongPassword!1',
                'ACCEPT_EULA': 'Y'
            })
            .withWaitStrategy(Wait.forLogMessage(/.*Emulator Service is Successfully Up!.*/))
            .withStartupTimeout(300000)
            .start();

        // Get the mapped port for Service Bus
        const sbPort = sbEmulatorContainer.getMappedPort(5672);

        // Additional delay for emulator to fully initialize
        // Tests show emulator needs extra time after log message before accepting connections
        await new Promise(resolve => setTimeout(resolve, 15000));

        const connectionString = `Endpoint=sb://localhost:${sbPort};SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;`;
        client = new ServiceBusClient(connectionString);
        sender = client.createSender(QUEUE_NAME);
    }, 360000); // 6 minutes for full container startup
    
    afterAll(async () => {
        if (sender) await sender.close();
        if (client) await client.close();
        if (sbEmulatorContainer) await sbEmulatorContainer.stop();
        if (sqlEdgeContainer) await sqlEdgeContainer.stop();
        if (network) await network.stop();
        if (configFilePath && fs.existsSync(configFilePath)) {
            fs.unlinkSync(configFilePath);
        }
    });
    
    {%- set message_list = messagegroup.messages.items() | list %}
    {%- for messageid, message in message_list %}
    {%- set messagename = messageid | strip_namespace | pascal %}
    {%- set body_type = util.body_type(data_project_name, root, message) %}
    {%- set isCloudEvent = cloudEvents.isCloudEvent(message) %}
    {%- set has_schema = message.dataschemauri or message.dataschema %}
    
    test('should receive and process {{ messagename }} message', async () => {
        const dispatcher = new {{ class_name }}();
        const processor = new ServiceBusProcessor(client);
        
        let messageHandled = false;
        let receivedData: any = null;
        {%- if isCloudEvent %}
        let receivedCloudEvent: CloudEvent<any> | null = null;
        {%- endif %}
        
        dispatcher.{{ messagename }}Handler = async (message, {% if isCloudEvent %}cloudEvent, {% endif %}data: {{ body_type }}) => {
            messageHandled = true;
            receivedData = data;
            {%- if isCloudEvent %}
            receivedCloudEvent = cloudEvent;
            {%- endif %}
        };
        
        processor.processMessage = async (message) => {
            await dispatcher.processMessage(message);
        };
        
        // Start processing from queue
        const processorPromise = processor.startQueue(QUEUE_NAME);
        
        // Give processor time to start
        await new Promise(resolve => setTimeout(resolve, 2000));
        
        // Send test message
        {%- if isCloudEvent %}
        {%- if message.envelopemetadata.type is defined %}
        {%- set type_value = message.envelopemetadata.type.value if message.envelopemetadata.type.value else messageid %}
        {%- else %}
        {%- set type_value = messageid %}
        {%- endif %}
        {%- if has_schema %}
        const testData = {{ body_type }}.createInstance();
        {%- else %}
        const testData = {} as {{ body_type }};
        {%- endif %}
        const testEvent = new CloudEvent({
            type: '{{ type_value }}',
            source: '/test/source',
            id: `test-${Date.now()}`,
            data: testData
        });
        
        const httpMessage = HTTP.binary(testEvent);
        const sbMessage = {
            body: httpMessage.body,
            contentType: httpMessage.headers['content-type'] || 'application/json',
            applicationProperties: httpMessage.headers
        };
        
        await sender.sendMessages(sbMessage);
        {%- else %}
        {%- if has_schema %}
        const testData = {{ body_type }}.createInstance();
        {%- else %}
        const testData = {} as {{ body_type }};
        {%- endif %}
        await sender.sendMessages({
            body: JSON.stringify(testData),
            contentType: 'application/json',
            subject: '{{ messageid }}'
        });
        {%- endif %}
        
        // Wait for message to be processed
        await new Promise(resolve => setTimeout(resolve, 5000));
        
        // Stop processing
        await processor.stop();
        
        expect(messageHandled).toBe(true);
        expect(receivedData).toBeDefined();
        {%- if isCloudEvent %}
        expect(receivedCloudEvent).toBeDefined();
        expect(receivedCloudEvent!.type).toBe('{{ type_value }}');
        {%- endif %}
    });
    {%- endfor %}
});
{%- endfor %}
