/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { execSync } from 'child_process';
import { readFileSync } from 'fs';
import { join } from 'path';

// Types
interface Version {
    tag: string;
    ref: string;
}

interface PR {
    number: number;
    title: string;
    commit: string;
}

// Configuration
const IGNORE_TYPES = [
    'docs',
    'chore',
    'test',
    'ci'
];

const IGNORE_PRS = [
    // 3.9.0
    10655, 10857, 10858, 10887, 10959, 11029, 11041, 11053, 11055, 11061, 10976, 10984, 11025,
    // 3.10.0
    11105, 11128, 11169, 11171, 11280, 11333, 11081, 11202, 11469,
    // 3.11.0
    11463, 11570,
    // 3.12.0
    11769, 11816, 11881, 11905, 11924, 11926, 11973, 11991, 11992, 11829,
    // 3.13.0
    9945, 11420, 11765, 12036, 12048, 12057, 12076, 12122, 12123, 12168, 12199, 12218, 12225, 12272, 12277, 12300, 12306, 12329, 12353, 12364, 12375, 12358
];


function getGitRef(version: string): string {
    try {
        execSync(`git rev-parse ${version}`, { stdio: 'ignore' });
        return version;
    } catch {
        return 'HEAD';
    }
}

function extractVersionsFromChangelog(): Version[] {
    const changelogPath = join(process.cwd(), '..', 'CHANGELOG.md');
    const content = readFileSync(changelogPath, 'utf-8');
    const versionRegex = /^## ([0-9]+\.[0-9]+\.[0-9]+)/gm;
    const versions: Version[] = [];
    let match;

    while ((match = versionRegex.exec(content)) !== null) {
        const tag = match[1];
        versions.push({
            tag,
            ref: getGitRef(tag)
        });
    }

    return versions;
}

function extractPRsFromChangelog(startTag: string, endTag: string): number[] {
    const changelogPath = join(process.cwd(), '..', 'CHANGELOG.md');
    const content = readFileSync(changelogPath, 'utf-8');
    const lines = content.split('\n');
    let inRange = false;
    const prs: number[] = [];

    for (const line of lines) {
        if (line.startsWith(`## ${startTag}`)) {
            inRange = true;
            continue;
        }
        if (inRange && line.startsWith(`## ${endTag}`)) {
            break;
        }
        if (inRange) {
            const match = line.match(/#(\d+)/);
            if (match) {
                prs.push(parseInt(match[1], 10));
            }
        }
    }

    return prs.sort((a, b) => a - b);
}

function shouldIgnoreCommitMessage(message: string): boolean {
    // Extract the commit message part (remove the commit hash)
    const messagePart = message.split(' ').slice(1).join(' ');

    // Check if the message starts with any of the ignored types
    for (const type of IGNORE_TYPES) {
        // Check simple format: "type: message"
        if (messagePart.startsWith(`${type}:`)) {
            return true;
        }
        // Check format with scope: "type(scope): message"
        if (messagePart.startsWith(`${type}(`)) {
            const closingBracketIndex = messagePart.indexOf('):');
            if (closingBracketIndex !== -1) {
                return true;
            }
        }
    }
    return false;
}

function extractPRsFromGitLog(oldRef: string, newRef: string): PR[] {
    const log = execSync(`git log ${oldRef}..${newRef} --oneline`, { encoding: 'utf-8' });
    const prs: PR[] = [];

    for (const line of log.split('\n')) {
        if (!line.trim()) continue;

        // Check if this commit should be ignored
        if (shouldIgnoreCommitMessage(line)) continue;

        // Find PR number
        const prMatch = line.match(/#(\d+)/);
        if (prMatch) {
            const prNumber = parseInt(prMatch[1], 10);
            if (!IGNORE_PRS.includes(prNumber)) {
                prs.push({
                    number: prNumber,
                    title: line,
                    commit: line.split(' ')[0]
                });
            }
        }
    }

    return prs.sort((a, b) => a.number - b.number);
}

function findMissingPRs(changelogPRs: number[], gitPRs: PR[]): PR[] {
    const changelogPRSet = new Set(changelogPRs);
    return gitPRs.filter(pr => !changelogPRSet.has(pr.number));
}

function versionGreaterThan(v1: string, v2: string): boolean {
    // Remove 'v' prefix if present
    const cleanV1 = v1.replace(/^v/, '');
    const cleanV2 = v2.replace(/^v/, '');

    // Split version strings into arrays of numbers
    const v1Parts = cleanV1.split('.').map(Number);
    const v2Parts = cleanV2.split('.').map(Number);

    // Compare each part
    for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
        const v1Part = v1Parts[i] || 0;
        const v2Part = v2Parts[i] || 0;

        if (v1Part > v2Part) return true;
        if (v1Part < v2Part) return false;
    }

    // If all parts are equal, return false
    return false;
}

// Main function
async function main() {
    try {
        const versions = extractVersionsFromChangelog();
        let hasErrors = false;

        for (let i = 0; i < versions.length - 1; i++) {
            const newVersion = versions[i];
            const oldVersion = versions[i + 1];

            // Skip if new version is less than or equal to 3.8.0
            if (!versionGreaterThan(newVersion.tag, '3.8.0')) {
                continue;
            }

            console.log(`\n=== Checking changes between ${newVersion.tag} (${newVersion.ref}) and ${oldVersion.tag} (${oldVersion.ref}) ===`);

            const changelogPRs = extractPRsFromChangelog(newVersion.tag, oldVersion.tag);
            const gitPRs = extractPRsFromGitLog(oldVersion.ref, newVersion.ref);
            const missingPRs = findMissingPRs(changelogPRs, gitPRs);

            console.log(`\n=== PR Comparison Results for ${newVersion.tag} ===`);

            if (missingPRs.length === 0) {
                console.log(`\n✅ All PRs are included in CHANGELOG.md for version ${newVersion.tag}`);
            } else {
                console.log(`\n❌ Missing PRs in CHANGELOG.md for version ${newVersion.tag} (sorted):`);
                missingPRs.forEach(pr => {
                    console.log(`  #${pr.number}`);
                });

                console.log(`\nDetailed information about missing PRs for version ${newVersion.tag}:`);
                missingPRs.forEach(pr => {
                    console.log(`\nPR #${pr.number}:`);
                    console.log(`  - ${pr.title}`);
                    console.log(`  - PR URL: https://github.com/apache/apisix/pull/${pr.number}`);
                });

                console.log('Note: If you confirm that a PR should not appear in the changelog, please add its number to the IGNORE_PRS array in this script.');
                hasErrors = true;
            }
        }

        if (hasErrors) {
            process.exit(1);
        }
    } catch (error) {
        console.error('Error:', error);
        process.exit(1);
    }
}

(async () => {
    await main();
})();
