/*
 * Elastic License 2.0
 * Copyright (C) 2025–present Raman Marozau, Work Target Insight Function. All rights reserved.
 * Contact: raman@worktif.com
 *
 * This file is part of the Licensed Work: worktif.runtime.cdk <worktif_runtime_cdk>.
 * Use of this software is governed by the Elastic License 2.0; see the LICENSE file
 * or https://www.elastic.co/licensing/elastic-license for details.
 *
 * SPDX-License-Identifier: Elastic-2.0
 */

/**
 * Tests for cdk.json.template content across all templates.
 *
 * **Validates: Requirements 4.1, 4.2, 4.4**
 * - 4.1: THE cdk.json.template SHALL include the `build` key with `cd .. && npx runtime build:microservices`
 * - 4.2: THE cdk.json.template SHALL preserve all existing CDK context flags and watch configuration
 * - 4.4: THE cdk.json.template SHALL include the correct relative path (`cd ..`) since cdk.json is in the cdk/ subdirectory
 */

import { describe, expect, it } from '@jest/globals';
import * as fs from 'fs';
import * as path from 'path';

/**
 * Template types available in the codebase.
 */
type TemplateType = 'basic' | 'advanced' | 'ai' | 'ai-chat';

/**
 * All template types to test.
 */
const ALL_TEMPLATES: TemplateType[] = ['basic', 'advanced', 'ai', 'ai-chat'];

/**
 * Expected build command value.
 */
const EXPECTED_BUILD_COMMAND = 'cd .. && npx runtime build:microservices';

/**
 * Expected app command value.
 */
const EXPECTED_APP_COMMAND = 'npx ts-node --prefer-ts-exts bin/app.ts';

/**
 * Required CDK context flags that must be preserved.
 * These are critical AWS CDK feature flags for consistent behavior.
 */
const REQUIRED_CONTEXT_FLAGS = [
    '@aws-cdk/aws-lambda:recognizeLayerVersion',
    '@aws-cdk/core:checkSecretUsage',
    '@aws-cdk/core:target-partitions',
    '@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId',
    '@aws-cdk/core:stackRelativeExports',
    '@aws-cdk/aws-iam:minimizePolicies',
    '@aws-cdk/core:validateSnapshotRemovalPolicy',
];

/**
 * Gets the path to a cdk.json.template file for a template type.
 *
 * @param templateType - The template type
 * @returns The absolute path to the cdk.json.template file
 */
function getCdkJsonTemplatePath(templateType: TemplateType): string {
    return path.join(__dirname, '..', templateType, 'cdk', 'cdk.json.template');
}

/**
 * Reads and parses a cdk.json.template file.
 *
 * @param templateType - The template type
 * @returns The parsed JSON content
 */
function readCdkJsonTemplate(templateType: TemplateType): Record<string, unknown> {
    const templatePath = getCdkJsonTemplatePath(templateType);
    const content = fs.readFileSync(templatePath, 'utf-8');
    return JSON.parse(content);
}

describe('cdk.json.template Content Tests', () => {
    /**
     * **Validates: Requirement 4.1**
     * THE cdk.json.template SHALL include the `build` key with `cd .. && npx runtime build:microservices`
     */
    describe('Requirement 4.1: Build Key Configuration', () => {
        it.each(ALL_TEMPLATES)('%s template should contain the "build" key', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson).toHaveProperty('build');
        });

        it.each(ALL_TEMPLATES)('%s template build command should be exactly "%s"', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson.build).toBe(EXPECTED_BUILD_COMMAND);
        });
    });

    /**
     * **Validates: Requirement 4.4**
     * THE cdk.json.template SHALL include the correct relative path (`cd ..`) since cdk.json is in the cdk/ subdirectory
     */
    describe('Requirement 4.4: Relative Path Configuration', () => {
        it.each(ALL_TEMPLATES)('%s template build command should use relative path "cd .."', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const buildCommand = cdkJson.build as string;

            // Verify the command starts with "cd .." to navigate from cdk/ to project root
            expect(buildCommand).toMatch(/^cd \.\./);
        });

        it.each(ALL_TEMPLATES)('%s template build command should navigate exactly one level up', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const buildCommand = cdkJson.build as string;

            // Extract the cd part and verify it's exactly "cd .." (not "cd ../.." or similar)
            const cdMatch = buildCommand.match(/^cd ([^ ]+)/);
            expect(cdMatch).not.toBeNull();
            expect(cdMatch![1]).toBe('..');
        });
    });

    /**
     * **Validates: Requirement 4.2**
     * THE cdk.json.template SHALL preserve all existing CDK context flags and watch configuration
     */
    describe('Requirement 4.2: Preserve App Key', () => {
        it.each(ALL_TEMPLATES)('%s template should contain the "app" key', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson).toHaveProperty('app');
        });

        it.each(ALL_TEMPLATES)('%s template app command should be "%s"', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson.app).toBe(EXPECTED_APP_COMMAND);
        });
    });

    /**
     * **Validates: Requirement 4.2**
     * THE cdk.json.template SHALL preserve all existing CDK context flags and watch configuration
     */
    describe('Requirement 4.2: Preserve Watch Configuration', () => {
        it.each(ALL_TEMPLATES)('%s template should contain the "watch" key', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson).toHaveProperty('watch');
        });

        it.each(ALL_TEMPLATES)('%s template watch should have "include" array', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const watch = cdkJson.watch as Record<string, unknown>;

            expect(watch).toHaveProperty('include');
            expect(Array.isArray(watch.include)).toBe(true);
        });

        it.each(ALL_TEMPLATES)('%s template watch should have "exclude" array', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const watch = cdkJson.watch as Record<string, unknown>;

            expect(watch).toHaveProperty('exclude');
            expect(Array.isArray(watch.exclude)).toBe(true);
        });

        it.each(ALL_TEMPLATES)('%s template watch include should contain "**"', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const watch = cdkJson.watch as Record<string, unknown>;
            const include = watch.include as string[];

            expect(include).toContain('**');
        });

        it.each(ALL_TEMPLATES)('%s template watch exclude should contain standard patterns', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const watch = cdkJson.watch as Record<string, unknown>;
            const exclude = watch.exclude as string[];

            // Standard exclusions that should be present
            expect(exclude).toContain('node_modules');
            expect(exclude).toContain('**/*.d.ts');
            expect(exclude).toContain('**/*.js');
        });
    });

    /**
     * **Validates: Requirement 4.2**
     * THE cdk.json.template SHALL preserve all existing CDK context flags and watch configuration
     */
    describe('Requirement 4.2: Preserve Context Flags', () => {
        it.each(ALL_TEMPLATES)('%s template should contain the "context" key', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson).toHaveProperty('context');
        });

        it.each(ALL_TEMPLATES)('%s template context should be an object', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const context = cdkJson.context;

            expect(typeof context).toBe('object');
            expect(context).not.toBeNull();
            expect(Array.isArray(context)).toBe(false);
        });

        it.each(ALL_TEMPLATES)('%s template should have required CDK context flags', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const context = cdkJson.context as Record<string, unknown>;

            for (const flag of REQUIRED_CONTEXT_FLAGS) {
                expect(context).toHaveProperty(flag);
            }
        });

        it.each(ALL_TEMPLATES)('%s template should have non-empty context flags', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            const context = cdkJson.context as Record<string, unknown>;
            const contextKeys = Object.keys(context);

            // Should have a substantial number of context flags (CDK best practices)
            expect(contextKeys.length).toBeGreaterThan(20);
        });
    });

    /**
     * Additional structural validation tests.
     */
    describe('Template Structure Validation', () => {
        it.each(ALL_TEMPLATES)('%s template file should exist', (templateType) => {
            const templatePath = getCdkJsonTemplatePath(templateType);
            expect(fs.existsSync(templatePath)).toBe(true);
        });

        it.each(ALL_TEMPLATES)('%s template should be valid JSON', (templateType) => {
            const templatePath = getCdkJsonTemplatePath(templateType);
            const content = fs.readFileSync(templatePath, 'utf-8');

            expect(() => JSON.parse(content)).not.toThrow();
        });

        it.each(ALL_TEMPLATES)('%s template should have "notices" key set to false', (templateType) => {
            const cdkJson = readCdkJsonTemplate(templateType);
            expect(cdkJson).toHaveProperty('notices', false);
        });
    });

    /**
     * Cross-template consistency tests.
     */
    describe('Cross-Template Consistency', () => {
        it('all templates should have identical build commands', () => {
            const buildCommands = ALL_TEMPLATES.map((templateType) => {
                const cdkJson = readCdkJsonTemplate(templateType);
                return cdkJson.build;
            });

            // All build commands should be the same
            const uniqueCommands = new Set(buildCommands);
            expect(uniqueCommands.size).toBe(1);
            expect(buildCommands[0]).toBe(EXPECTED_BUILD_COMMAND);
        });

        it('all templates should have identical app commands', () => {
            const appCommands = ALL_TEMPLATES.map((templateType) => {
                const cdkJson = readCdkJsonTemplate(templateType);
                return cdkJson.app;
            });

            // All app commands should be the same
            const uniqueCommands = new Set(appCommands);
            expect(uniqueCommands.size).toBe(1);
            expect(appCommands[0]).toBe(EXPECTED_APP_COMMAND);
        });

        it('all templates should have the same required context flags', () => {
            for (const flag of REQUIRED_CONTEXT_FLAGS) {
                const flagValues = ALL_TEMPLATES.map((templateType) => {
                    const cdkJson = readCdkJsonTemplate(templateType);
                    const context = cdkJson.context as Record<string, unknown>;
                    return context[flag];
                });

                // All templates should have the same value for each required flag
                const uniqueValues = new Set(flagValues.map((v) => JSON.stringify(v)));
                expect(uniqueValues.size).toBe(1);
            }
        });
    });
});
