'use strict';

/**
 * Diff Reporter — format delta dari diff-engine menjadi output human-readable
 * plain text atau JSON object. Reporter tidak melakukan I/O sendiri; caller
 * yang memutuskan untuk write ke stdout atau file.
 *
 * Exit code semantic:
 *   0 — no drift
 *   1 — drift detected
 *   2 — error (di-set oleh caller, bukan oleh reporter ini)
 *
 * @module lib/dbschema-kit/diff-reporter
 */

// ─────────────────────────────────────────────────────────────
// Format helpers
// ─────────────────────────────────────────────────────────────

function formatFieldSignature(f) {
    if (!f) return '(unknown)';
    const parts = [];
    if (f.type !== undefined) {
        if (f.type === 'string' && f.length !== undefined) {
            parts.push(`${f.type}(${f.length})`);
        } else if (f.type === 'decimal' && f.precision !== undefined && f.scale !== undefined) {
            parts.push(`${f.type}(${f.precision},${f.scale})`);
        } else {
            parts.push(String(f.type));
        }
    }
    parts.push(f.nullable === false ? 'NOT NULL' : 'NULL');
    return parts.join(' ');
}

function formatFieldLine(f) {
    return `    - ${f.name}  (${formatFieldSignature(f)})`;
}

function formatColumns(cols) {
    if (!Array.isArray(cols) || cols.length === 0) return '(empty)';
    return cols.join(', ');
}

function formatFkLine(fk) {
    if (!fk) return '    - (invalid)';
    const refCol = fk.references ? `.${fk.references}` : '';
    const actions = [];
    if (fk.onDelete && fk.onDelete !== 'restrict') {
        actions.push(`ON DELETE ${String(fk.onDelete).toUpperCase()}`);
    }
    if (fk.onUpdate && fk.onUpdate !== 'restrict') {
        actions.push(`ON UPDATE ${String(fk.onUpdate).toUpperCase()}`);
    }
    const actionStr = actions.length > 0 ? `  [${actions.join(', ')}]` : '';
    return `    - ${fk.localKey} -> ${fk.target}${refCol}${actionStr}`;
}

function formatFkMismatchedLine(fk) {
    if (!fk) return '    - (invalid)';
    const refCol = fk.references ? `.${fk.references}` : '';
    const lines = [`    - ${fk.localKey} -> ${fk.target}${refCol}`];
    if (Array.isArray(fk.reasons)) {
        for (const r of fk.reasons) {
            lines.push(`        ${r}`);
        }
    }
    return lines.join('\n');
}

function formatCheckValue(value) {
    if (Array.isArray(value)) {
        const items = value.map((v) => (typeof v === 'string' ? `'${v}'` : String(v)));
        return `[${items.join(', ')}]`;
    }
    if (typeof value === 'string') return `'${value}'`;
    if (value === null || value === undefined) return 'null';
    return String(value);
}

function formatCheckLine(c) {
    if (!c) return '    - (invalid)';
    if (c._unparsable) return `    - (unparsable) ${c.expression}`;
    return `    - ${c.field}: ${c.op} ${formatCheckValue(c.value)}`;
}

// ─────────────────────────────────────────────────────────────
// Section builders (human format)
// ─────────────────────────────────────────────────────────────

function buildFieldsSection(fields) {
    const lines = ['Fields:'];

    lines.push('  + Only in SDF:');
    if (fields.onlyInSdf.length === 0) {
        lines.push('    (none)');
    } else {
        for (const f of fields.onlyInSdf) lines.push(formatFieldLine(f));
    }

    lines.push('  - Only in DB:');
    if (fields.onlyInDb.length === 0) {
        lines.push('    (none)');
    } else {
        for (const f of fields.onlyInDb) lines.push(formatFieldLine(f));
    }

    lines.push('  ~ Mismatched:');
    if (fields.mismatched.length === 0) {
        lines.push('    (none)');
    } else {
        for (const m of fields.mismatched) {
            lines.push(`    - ${m.name}`);
            for (const r of m.reasons) {
                lines.push(`        ${r}`);
            }
        }
    }

    return lines;
}

function buildPrimaryKeySection(pk) {
    const lines = ['Primary Key:'];
    if (pk.match) {
        const cols = pk.sdf.length === 0 ? '(none)' : pk.sdf.join(', ');
        lines.push(`  Match: ${cols}`);
    } else {
        lines.push(`  Mismatch:`);
        lines.push(`    SDF: ${formatColumns(pk.sdf)}`);
        lines.push(`    DB:  ${formatColumns(pk.db)}`);
    }
    return lines;
}

function buildCollectionSection(label, coll, formatter, mismatchedFormatter) {
    const isEmpty = coll.onlyInSdf.length === 0 && coll.onlyInDb.length === 0 && coll.mismatched.length === 0;
    if (isEmpty) {
        return [`${label}: (no drift)`];
    }

    const lines = [`${label}:`];
    lines.push('  + Only in SDF:');
    if (coll.onlyInSdf.length === 0) {
        lines.push('    (none)');
    } else {
        for (const it of coll.onlyInSdf) lines.push(formatter(it));
    }
    lines.push('  - Only in DB:');
    if (coll.onlyInDb.length === 0) {
        lines.push('    (none)');
    } else {
        for (const it of coll.onlyInDb) lines.push(formatter(it));
    }
    lines.push('  ~ Mismatched:');
    if (coll.mismatched.length === 0) {
        lines.push('    (none)');
    } else {
        const useMismatch = typeof mismatchedFormatter === 'function' ? mismatchedFormatter : formatter;
        for (const it of coll.mismatched) lines.push(useMismatch(it));
    }
    return lines;
}

function formatColumnItem(item) {
    const cols = Array.isArray(item.columns) ? item.columns.join(', ') : '(empty)';
    return `    - ${cols}`;
}

function buildTableReport(delta, idx, total) {
    const lines = [];
    const banner = `Drift Report: ${delta.tableName} (${idx}/${total} table compared)`;
    lines.push(banner);
    lines.push('='.repeat(Math.max(banner.length, 30)));
    lines.push('');

    if (!delta.hasDrift) {
        lines.push('No drift detected. SDF and database are in sync.');
        return lines;
    }

    lines.push(...buildFieldsSection(delta.fields));
    lines.push('');
    lines.push(...buildPrimaryKeySection(delta.primaryKey));
    lines.push('');
    lines.push(...buildCollectionSection('Indexes', delta.indexes, formatColumnItem));
    lines.push('');
    lines.push(...buildCollectionSection('Uniques', delta.uniques, formatColumnItem));
    lines.push('');
    lines.push(...buildCollectionSection('Foreign Keys', delta.foreignKeys, formatFkLine, formatFkMismatchedLine));
    if (delta.checks) {
        lines.push('');
        lines.push(...buildCollectionSection('Checks', delta.checks, formatCheckLine));
    }

    return lines;
}

// ─────────────────────────────────────────────────────────────
// Public API: human / json / exit code
// ─────────────────────────────────────────────────────────────

function reportHuman(deltas) {
    const arr = Array.isArray(deltas) ? deltas : [];
    const total = arr.length;
    const tablesWithDrift = arr.filter((d) => d && d.hasDrift).length;
    const tablesClean = total - tablesWithDrift;

    const lines = [];
    if (total === 0) {
        lines.push('No tables compared. Schema set is empty.');
        lines.push('Exit code: 0');
        return lines.join('\n');
    }

    for (let i = 0; i < arr.length; i++) {
        const delta = arr[i];
        lines.push(...buildTableReport(delta, i + 1, total));
        if (i < arr.length - 1) {
            lines.push('');
            lines.push('');
        }
    }

    lines.push('');
    lines.push(`Summary: ${tablesWithDrift} table${tablesWithDrift === 1 ? '' : 's'} with drift, ${tablesClean} clean`);
    const exitCode = tablesWithDrift > 0 ? 1 : 0;
    lines.push(`Exit code: ${exitCode}`);

    return lines.join('\n');
}

function reportJson(deltas) {
    const arr = Array.isArray(deltas) ? deltas : [];
    const tablesWithDrift = arr.filter((d) => d && d.hasDrift).length;
    const tablesClean = arr.length - tablesWithDrift;
    const exitCode = tablesWithDrift > 0 ? 1 : 0;

    return {
        version: '1.0',
        summary: {
            totalTables: arr.length,
            tablesWithDrift,
            tablesClean,
            exitCode
        },
        tables: arr.map((d) => {
            const out = {
                tableName: d.tableName,
                hasDrift: d.hasDrift,
                fields: d.fields,
                primaryKey: d.primaryKey,
                indexes: d.indexes,
                uniques: d.uniques,
                foreignKeys: d.foreignKeys
            };
            if (d.checks) out.checks = d.checks;
            return out;
        })
    };
}

function getExitCode(deltas) {
    const arr = Array.isArray(deltas) ? deltas : [];
    return arr.some((d) => d && d.hasDrift) ? 1 : 0;
}

module.exports = {
    reportHuman,
    reportJson,
    getExitCode
};
