const { Pool } = require('pg');
const bcrypt = require('bcryptjs');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });

const pool = new Pool({
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_DATABASE || process.env.DB_NAME,
    password: process.env.DB_PASSWORD,
    port: process.env.DB_PORT,
});

const USER_COUNT = 20000;
const ACHIEVEMENT_COUNT = 500000;
const BATCH_SIZE = 1000;
const RUN_CLEAN = process.argv.includes('--clean');

const dictTables = [
    'dict_organizations',
    'dict_award_types',
    'dict_award_levels',
    'dict_paper_types',
    'dict_standard_types',
    'dict_project_categories',
];

const achievementTypes = [
    'paper',
    'award',
    'project',
    'standard',
    'monograph',
    'report',
    'plan',
    'patent',
    'transformation',
    'software',
];
const USERNAME_PREFIX = '176';
const SEED_REMARK = 'seed_500k';
const STATUSES = ['pending', 'approved', 'rejected'];
const PATENT_TYPES = ['invention', 'utility', 'design'];
const REPORT_TYPES = ['technical', 'inspection', 'analysis', 'feasibility'];
const TRANS_METHODS = ['transfer', 'license', 'cooperation', 'self-use'];
const ACQUISITION_METHODS = ['independent', 'co-development', 'transfer'];
const SOFTWARE_SCOPES = ['national', 'industry', 'department'];

const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const randomChoice = (arr) => arr[randomInt(0, arr.length - 1)];
const pad = (num, size) => String(num).padStart(size, '0');
const randomDateInYears = (years) =>
    new Date(randomChoice(years), randomInt(0, 11), randomInt(1, 28));

function buildInsert(table, columns, rows) {
    const values = [];
    const params = [];
    let paramIndex = 1;

    for (const row of rows) {
        const placeholders = [];
        for (const value of row) {
            params.push(value);
            placeholders.push(`$${paramIndex++}`);
        }
        values.push(`(${placeholders.join(', ')})`);
    }

    const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES ${values.join(', ')}`;
    return { sql, params };
}

async function fetchNames(client, table) {
    const res = await client.query(`SELECT name FROM ${table} ORDER BY id`);
    return res.rows.map((row) => row.name);
}

async function seedUsers(client, departments) {
    const passwordHash = await bcrypt.hash('123456', 10);
    let created = 0;

    for (let offset = 0; offset < USER_COUNT; offset += BATCH_SIZE) {
        const batchSize = Math.min(BATCH_SIZE, USER_COUNT - offset);
        const rows = [];

        for (let i = 0; i < batchSize; i++) {
            const index = offset + i;
            const username = `${USERNAME_PREFIX}${pad(index, 8)}`; // 11 digits, deterministic
            const realName = `Perf User ${index + 1}`;
            const department = randomChoice(departments);
            rows.push([username, passwordHash, 'user', realName, department, 'verified']);
        }

        const { sql, params } = buildInsert(
            'users',
            ['username', 'password_hash', 'role', 'real_name', 'department', 'dept_status'],
            rows
        );

        await client.query(
            `${sql} ON CONFLICT (username) DO UPDATE SET
                password_hash = EXCLUDED.password_hash,
                role = EXCLUDED.role,
                real_name = EXCLUDED.real_name,
                department = EXCLUDED.department,
                dept_status = EXCLUDED.dept_status`
        , params);

        created += batchSize;
        if (created % 5000 === 0) {
            console.log(`[seed-data-500k] users upserted: ${created}`);
        }
    }
}

async function seedAchievements(client, departments, dicts, userIds) {
    let inserted = 0;
    const dateYears = [2023, 2024, 2026];

    for (let offset = 0; offset < ACHIEVEMENT_COUNT; offset += BATCH_SIZE) {
        const batchSize = Math.min(BATCH_SIZE, ACHIEVEMENT_COUNT - offset);
        await client.query('BEGIN');
        try {
            const rows = [];
            const achievementDates = [];

            for (let i = 0; i < batchSize; i++) {
                const index = offset + i;
                const userId = randomChoice(userIds);
                const type = randomChoice(achievementTypes);
                const name = `Perf Achievement ${index + 1}`;
                const assignedDept = randomChoice(departments);
                const date = randomDateInYears(dateYears);
                achievementDates.push(date);
                const contributors = [
                    { name: `Contributor ${index + 1}A`, phone: `130${pad(index % 100000000, 8)}`, isMain: true },
                    { name: `Contributor ${index + 1}B`, phone: `131${pad(index % 100000000, 8)}`, isMain: false },
                ];
                rows.push([
                    userId,
                    type,
                    name,
                    JSON.stringify(contributors),
                    [assignedDept],
                    date,
                    randomChoice(STATUSES),
                    SEED_REMARK,
                    date,
                ]);
            }

            const { sql, params } = buildInsert(
                'achievements',
                [
                    'user_id',
                    'type',
                    'name',
                    'contributors',
                    'assigned_departments',
                    'achievement_date',
                    'status',
                    'remarks',
                    'created_at',
                ],
                rows
            );

            const res = await client.query(`${sql} RETURNING id, type`, params);

            const attachmentsRows = [];
            const paperRows = [];
            const awardRows = [];
            const projectRows = [];
            const standardRows = [];
            const monographRows = [];
            const reportRows = [];
            const planRows = [];
            const patentRows = [];
            const transformationRows = [];
            const softwareRows = [];

            for (let i = 0; i < res.rows.length; i++) {
                const { id, type } = res.rows[i];
                const date = achievementDates[i];
                const idx = offset + i + 1;
                attachmentsRows.push([id, `attachment_${idx}.pdf`, 'uploads/mock_file.pdf']);

                if (type === 'paper') {
                    paperRows.push([id, randomChoice(dicts.dict_paper_types), 'Perf Journal', randomDateInYears(dateYears)]);
                } else if (type === 'award') {
                    awardRows.push([
                        id,
                        randomChoice(dicts.dict_award_types),
                        randomChoice(dicts.dict_award_levels),
                        randomChoice(dicts.dict_organizations),
                    ]);
                } else if (type === 'project') {
                    projectRows.push([id, randomChoice(dicts.dict_project_categories), 'Perf Project Source']);
                } else if (type === 'standard') {
                    const standardYear = date.getFullYear();
                    standardRows.push([
                        id,
                        randomChoice(dicts.dict_standard_types),
                        `STD-${standardYear}-${pad(idx, 6)}`,
                        date,
                    ]);
                } else if (type === 'monograph') {
                    monographRows.push([id, 'Perf Press', `ISBN-${pad(idx, 9)}`]);
                } else if (type === 'report') {
                    reportRows.push([id, randomChoice(REPORT_TYPES), 'Recipient Unit', null, `Approver ${idx}`]);
                } else if (type === 'plan') {
                    planRows.push([id]);
                } else if (type === 'patent') {
                    patentRows.push([id, `PAT-${pad(idx, 8)}`, randomChoice(PATENT_TYPES), 'Perf Assignee']);
                } else if (type === 'transformation') {
                    transformationRows.push([
                        id,
                        randomChoice(TRANS_METHODS),
                        randomInt(50, 5000) * 1000 + randomInt(0, 99) / 100,
                        randomChoice(dicts.dict_project_categories),
                    ]);
                } else if (type === 'software') {
                    softwareRows.push([
                        id,
                        `SW-${pad(idx, 8)}`,
                        randomChoice(ACQUISITION_METHODS),
                        randomChoice(SOFTWARE_SCOPES),
                        'Perf Owner Unit',
                    ]);
                }
            }

            if (attachmentsRows.length) {
                const insert = buildInsert(
                    'achievement_attachments',
                    ['achievement_id', 'file_name', 'file_path'],
                    attachmentsRows
                );
                await client.query(insert.sql, insert.params);
            }

            if (paperRows.length) {
                const insert = buildInsert(
                    'achievement_paper',
                    ['achievement_id', 'paper_type', 'journal_name', 'publish_date'],
                    paperRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (awardRows.length) {
                const insert = buildInsert(
                    'achievement_award',
                    ['achievement_id', 'award_type', 'award_level', 'award_unit'],
                    awardRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (projectRows.length) {
                const insert = buildInsert(
                    'achievement_project',
                    ['achievement_id', 'project_category', 'source'],
                    projectRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (standardRows.length) {
                const insert = buildInsert(
                    'achievement_standard',
                    ['achievement_id', 'standard_type', 'standard_no', 'implement_date'],
                    standardRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (monographRows.length) {
                const insert = buildInsert(
                    'achievement_monograph',
                    ['achievement_id', 'publisher', 'isbn'],
                    monographRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (reportRows.length) {
                const insert = buildInsert(
                    'achievement_report',
                    ['achievement_id', 'report_type', 'recipient', 'approver_id', 'approver_name'],
                    reportRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (planRows.length) {
                const insert = buildInsert(
                    'achievement_plan',
                    ['achievement_id'],
                    planRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (patentRows.length) {
                const insert = buildInsert(
                    'achievement_patent',
                    ['achievement_id', 'patent_no', 'patent_type', 'assignee'],
                    patentRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (transformationRows.length) {
                const insert = buildInsert(
                    'achievement_transformation',
                    ['achievement_id', 'trans_method', 'trans_amount', 'project_category'],
                    transformationRows
                );
                await client.query(insert.sql, insert.params);
            }
            if (softwareRows.length) {
                const insert = buildInsert(
                    'achievement_software',
                    ['achievement_id', 'reg_no', 'acquisition_method', 'scope', 'owner_unit'],
                    softwareRows
                );
                await client.query(insert.sql, insert.params);
            }

            await client.query('COMMIT');
        } catch (err) {
            await client.query('ROLLBACK');
            throw err;
        }
        inserted += batchSize;
        if (inserted % 10000 === 0) {
                console.log(`[seed-data-500k] achievements inserted: ${inserted}`);
        }
    }
}

async function cleanSeedData(client) {
    console.log('[seed-data-500k] cleaning existing seeded data...');
    await client.query('BEGIN');
    try {
        const usersRes = await client.query(
            'SELECT id FROM users WHERE username LIKE $1',
            [`${USERNAME_PREFIX}%`]
        );
        const userIds = usersRes.rows.map((row) => row.id);

        await client.query(
            'DELETE FROM achievements WHERE remarks = $1 OR user_id = ANY($2)',
            [SEED_REMARK, userIds.length ? userIds : [0]]
        );

        await client.query(
            'DELETE FROM users WHERE username LIKE $1',
            [`${USERNAME_PREFIX}%`]
        );

        await client.query('COMMIT');
        console.log('[seed-data-500k] clean done');
    } catch (err) {
        await client.query('ROLLBACK');
        throw err;
    }
}

async function main() {
    const client = await pool.connect();
    try {
        console.log('[seed-data-500k] start');

        if (RUN_CLEAN) {
            await cleanSeedData(client);
        }

        const departments = await fetchNames(client, 'departments');
        if (departments.length === 0) {
            throw new Error('No departments found. Run "npm run init-db" first.');
        }

        const dicts = {};
        for (const table of dictTables) {
            const values = await fetchNames(client, table);
            if (values.length === 0) {
                throw new Error(`No data in ${table}. Run "npm run init-db" first.`);
            }
            dicts[table] = values;
        }

        await client.query('BEGIN');
        await seedUsers(client, departments);
        await client.query('COMMIT');

        const userIdsRes = await client.query(
            'SELECT id FROM users WHERE username LIKE $1 ORDER BY id',
            [`${USERNAME_PREFIX}%`]
        );
        const userIds = userIdsRes.rows.map((row) => row.id);
        if (userIds.length === 0) {
            throw new Error('No seeded users found.');
        }

        await seedAchievements(client, departments, dicts, userIds);

        console.log('[seed-data-500k] done');
    } catch (err) {
        await client.query('ROLLBACK');
        console.error('[seed-data-500k] failed:', err.message || err);
        process.exitCode = 1;
    } finally {
        client.release();
        await pool.end();
    }
}

main();
