<?php
namespace App\Controllers;

use App\Core\Audit;
use App\Core\Auth;
use App\Core\Config;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Dates;
use App\Core\Flash;
use App\Core\Request;
use App\Core\Response;
use App\Core\Session;
use App\Core\Settings;
use App\Services\ExportService;
use App\Services\ImportService;
use App\Services\RevisionService;

/** Wizard import Excel 10 langkah + riwayat import. */
class ImportController extends Controller
{
    protected string $module = 'import';

    private const SESSION_KEY = '_import_wizard';

    public function index(): void
    {
        [$page, $per] = $this->pageParams();
        $db = Database::i();
        $sql = 'SELECT ib.*, u.name AS uploaded_by_name, ur.name AS rolled_back_by_name
                  FROM import_batches ib
                  LEFT JOIN users u ON u.id = ib.uploaded_by
                  LEFT JOIN users ur ON ur.id = ib.rolled_back_by
                 WHERE ib.deleted_at IS NULL ORDER BY ib.uploaded_at DESC, ib.id DESC';
        $m = new \App\Models\ImportBatch();
        $this->view('import/index', [
            'judul' => 'Import Data',
            'subjudul' => 'Riwayat impor berkas Excel/CSV ke dalam sistem',
            'p' => $m->paginate($sql, [], $page, $per, 'SELECT COUNT(*) FROM import_batches WHERE deleted_at IS NULL'),
            'stat' => $db->one('SELECT COUNT(*) total, COALESCE(SUM(success_rows),0) sukses,
                                       COALESCE(SUM(failed_rows),0) gagal, COALESCE(SUM(total_rows),0) baris
                                  FROM import_batches WHERE deleted_at IS NULL AND status <> "rolled_back"'),
        ]);
    }

    public function wizard(): void
    {
        $state = Session::get(self::SESSION_KEY, []);
        $this->view('import/wizard', [
            'judul' => 'Wizard Import Excel',
            'subjudul' => 'Sepuluh langkah terpandu: unggah, pilih sheet, petakan kolom, validasi, dan simpan',
            'state' => $state,
            'fields' => ImportService::FIELDS,
            'fieldsRf' => ImportService::FIELDS_RF,
            'maxUpload' => min(
                self::parseSize((string)ini_get('upload_max_filesize')),
                self::parseSize((string)ini_get('post_max_size'))
            ),
        ]);
    }

    private static function parseSize(string $s): int
    {
        $s = trim($s);
        if ($s === '') return 0;
        $unit = strtolower(substr($s, -1));
        $n = (int)$s;
        return match ($unit) { 'g' => $n * 1073741824, 'm' => $n * 1048576, 'k' => $n * 1024, default => $n };
    }

    /** Langkah 1–2: unggah berkas dan tampilkan daftar sheet. */
    public function upload(): void
    {
        $f = Request::file('berkas');
        if (!$f) $this->json(['sukses' => false, 'pesan' => 'Tidak ada berkas yang diunggah.'], 422);
        if ($f['error'] !== UPLOAD_ERR_OK) {
            $msg = [UPLOAD_ERR_INI_SIZE => 'Ukuran berkas melebihi batas server.',
                    UPLOAD_ERR_FORM_SIZE => 'Ukuran berkas melebihi batas formulir.',
                    UPLOAD_ERR_PARTIAL => 'Berkas hanya terunggah sebagian.',
                    UPLOAD_ERR_NO_TMP_DIR => 'Direktori sementara tidak tersedia di server.',
                    UPLOAD_ERR_CANT_WRITE => 'Berkas gagal ditulis ke disk.'][$f['error']] ?? 'Berkas gagal diunggah.';
            $this->json(['sukses' => false, 'pesan' => $msg], 422);
        }
        $ext = strtolower(pathinfo((string)$f['name'], PATHINFO_EXTENSION));
        if (!in_array($ext, ['xlsx', 'xlsm', 'csv', 'txt'], true)) {
            $this->json(['sukses' => false, 'pesan' => 'Format berkas tidak didukung. Gunakan .xlsx atau .csv.'], 422);
        }

        $dir = rtrim((string)Config::get('paths.tmp'), '/') . '/import';
        if (!is_dir($dir)) @mkdir($dir, 0775, true);
        $stored = 'imp_' . date('Ymd_His') . '_' . bin2hex(random_bytes(5)) . '.' . $ext;
        $path = $dir . '/' . $stored;
        if (!move_uploaded_file($f['tmp_name'], $path)) {
            $this->json(['sukses' => false, 'pesan' => 'Berkas gagal disimpan di server.'], 500);
        }

        try {
            $svc = new ImportService($path);
            $sheets = $svc->sheets();
        } catch (\Throwable $e) {
            @unlink($path);
            $this->json(['sukses' => false, 'pesan' => $e->getMessage()], 422);
        }

        $info = [];
        foreach ($sheets as $s) {
            $probe = new ImportService($path, ['source_sheet' => $s['name']]);
            $type = $probe->guessType($s['name']);
            $g = (new ImportService($path, ['source_sheet' => $s['name'], 'import_type' => $type]))->guessMapping($s['name']);
            $info[] = [
                'nama' => $s['name'], 'state' => $s['state'], 'jenis' => $type,
                'kolom_terpetakan' => count($g['mapping']),
                'kolom_diabaikan' => array_values($g['diabaikan']),
                'kolom_tak_dikenali' => array_values($g['tidak_terpetakan']),
                'header' => $g['headers'],
            ];
        }

        Session::set(self::SESSION_KEY, [
            'path' => $path, 'nama_asli' => $f['name'], 'ukuran' => (int)$f['size'],
            'diunggah' => Dates::now(), 'sheets' => $info,
        ]);
        $this->json(['sukses' => true, 'nama' => $f['name'], 'ukuran' => (int)$f['size'], 'sheets' => $info]);
    }

    /** Langkah 3–5: pratinjau baris & pemetaan kolom. */
    public function preview(): void
    {
        $state = Session::get(self::SESSION_KEY, []);
        if (empty($state['path']) || !is_file($state['path'])) {
            $this->json(['sukses' => false, 'pesan' => 'Sesi import berakhir. Silakan unggah berkas kembali.'], 400);
        }
        $sheet = (string)Request::post('sheet', '');
        $type = (string)Request::post('jenis', '');
        $svc = new ImportService($state['path'], ['source_sheet' => $sheet]);
        if ($type === '') $type = $svc->guessType($sheet);
        $svc = new ImportService($state['path'], ['source_sheet' => $sheet, 'import_type' => $type]);
        $g = $svc->guessMapping($sheet);

        $fields = [];
        foreach ($svc->fields() as $k => $d) {
            $fields[] = ['key' => $k, 'label' => $d['label'], 'group' => $d['group'], 'wajib' => !empty($d['required'])];
        }
        $this->json([
            'sukses' => true, 'jenis' => $type,
            'header' => $g['headers'], 'mapping' => $g['mapping'],
            'diabaikan' => $g['diabaikan'], 'tak_dikenali' => $g['tidak_terpetakan'],
            'fields' => $fields,
            'preview' => $svc->preview($sheet, Settings::int('import.baris_preview', 15)),
        ]);
    }

    /** Langkah 7–8: validasi tanpa menyimpan. */
    public function validateData(): void
    {
        $state = Session::get(self::SESSION_KEY, []);
        if (empty($state['path']) || !is_file($state['path'])) {
            $this->json(['sukses' => false, 'pesan' => 'Sesi import berakhir. Silakan unggah berkas kembali.'], 400);
        }
        $sheet = (string)Request::post('sheet', '');
        $type = (string)Request::post('jenis', 'invoice');
        $mapping = json_decode((string)Request::post('mapping', '{}'), true) ?: [];
        $opsi = json_decode((string)Request::post('opsi', '{}'), true) ?: [];

        $svc = new ImportService($state['path'], [
            'source_sheet' => $sheet, 'import_type' => $type,
            'auto_buat_master' => (bool)($opsi['auto_buat_master'] ?? true),
            'perbarui_duplikat' => (bool)($opsi['perbarui_duplikat'] ?? false),
            'buat_pembayaran' => (bool)($opsi['buat_pembayaran'] ?? true),
        ]);
        try {
            $res = $svc->run($sheet, $mapping, true, null);
        } catch (\Throwable $e) {
            $this->json(['sukses' => false, 'pesan' => 'Validasi gagal: ' . $e->getMessage()], 500);
        }
        $contoh = array_slice(array_values(array_filter($res['baris'], fn($b) => $b['status'] !== 'ok')), 0, 200);
        $this->json([
            'sukses' => true,
            'ringkasan' => [
                'total' => $res['total'], 'sukses' => $res['sukses'], 'gagal' => $res['gagal'],
                'duplikat' => $res['duplikat'], 'peringatan' => $res['peringatan'], 'dilewati' => $res['dilewati'],
            ],
            'ringkasan_error' => $res['ringkasan_error'],
            'baris_bermasalah' => $contoh,
        ]);
    }

    /** Langkah 9–10: menyimpan data. */
    public function run(): void
    {
        $state = Session::get(self::SESSION_KEY, []);
        if (empty($state['path']) || !is_file($state['path'])) {
            $this->json(['sukses' => false, 'pesan' => 'Sesi import berakhir. Silakan unggah berkas kembali.'], 400);
        }
        $sheet = (string)Request::post('sheet', '');
        $type = (string)Request::post('jenis', 'invoice');
        $mapping = json_decode((string)Request::post('mapping', '{}'), true) ?: [];
        $opsi = json_decode((string)Request::post('opsi', '{}'), true) ?: [];

        @set_time_limit(600);
        $db = Database::i();
        $batchId = ImportService::createBatch([
            'file_name' => (string)($state['nama_asli'] ?? 'berkas.xlsx'),
            'stored_name' => basename((string)$state['path']),
            'file_size' => (int)($state['ukuran'] ?? 0),
            'sheet_name' => $sheet,
            'import_type' => $type,
            'mapping_json' => json_encode($mapping, JSON_UNESCAPED_UNICODE),
            'options_json' => json_encode($opsi, JSON_UNESCAPED_UNICODE),
            'status' => 'processing',
        ]);

        $svc = new ImportService($state['path'], [
            'source_sheet' => $sheet, 'import_type' => $type,
            'auto_buat_master' => (bool)($opsi['auto_buat_master'] ?? true),
            'perbarui_duplikat' => (bool)($opsi['perbarui_duplikat'] ?? false),
            'buat_pembayaran' => (bool)($opsi['buat_pembayaran'] ?? true),
        ]);

        $db->begin();
        try {
            $res = $svc->run($sheet, $mapping, false, $batchId);
            $db->commit();
        } catch (\Throwable $e) {
            $db->rollback();
            $db->update('import_batches', ['status' => 'failed', 'error_summary' => $e->getMessage(),
                'finished_at' => Dates::now()], 'id = :pk', ['pk' => $batchId]);
            $this->json(['sukses' => false, 'pesan' => 'Import gagal dan seluruh perubahan dibatalkan: ' . $e->getMessage()], 500);
        }
        ImportService::finishBatch($batchId, $res);

        $rf = null;
        if ($type === 'revision') {
            $rf = RevisionService::autoApplyMatched();
        }

        Audit::log('import', 'import', 'import_batch', $batchId,
            'Import ' . ($state['nama_asli'] ?? '') . ' sheet "' . $sheet . '": '
            . $res['sukses'] . ' sukses, ' . $res['gagal'] . ' gagal', null, $res['ringkasan_error'] ?: null);

        $this->json([
            'sukses' => true, 'batch_id' => $batchId,
            'ringkasan' => [
                'total' => $res['total'], 'sukses' => $res['sukses'], 'gagal' => $res['gagal'],
                'duplikat' => $res['duplikat'], 'peringatan' => $res['peringatan'], 'dilewati' => $res['dilewati'],
            ],
            'master_baru' => $res['master_baru'],
            'revisi' => $rf,
            'redirect' => url('/import/' . $batchId),
            'pesan' => 'Import selesai: ' . $res['sukses'] . ' baris tersimpan, ' . $res['gagal'] . ' baris gagal.',
        ]);
    }

    public function show(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $db = Database::i();
        $b = $db->one('SELECT ib.*, u.name AS uploaded_by_name FROM import_batches ib
                       LEFT JOIN users u ON u.id = ib.uploaded_by WHERE ib.id = :id', ['id' => $id]);
        if (!$b) Response::abort(404, 'Batch import tidak ditemukan.');
        [$page, $per] = $this->pageParams();
        $statusFilter = (string)Request::get('status', '');
        $w = 'ir.import_batch_id = :b';
        $p = ['b' => $id];
        if ($statusFilter !== '') { $w .= ' AND ir.status = :s'; $p['s'] = $statusFilter; }

        $m = new \App\Models\ImportBatch();
        $rows = $m->paginate(
            "SELECT ir.*, i.invoice_no FROM import_rows ir LEFT JOIN invoices i ON i.id = ir.invoice_id
              WHERE $w ORDER BY ir.row_number", $p, $page, $per,
            "SELECT COUNT(*) FROM import_rows ir WHERE $w"
        );

        $this->view('import/detail', [
            'judul' => 'Detail Import ' . $b['batch_no'],
            'subjudul' => $b['file_name'] . ' · sheet "' . $b['sheet_name'] . '" · ' . tglWaktu($b['uploaded_at']),
            'b' => $b, 'p' => $rows, 'statusFilter' => $statusFilter,
            'ringkasan' => arr($b['error_summary']),
            'mapping' => arr($b['mapping_json']),
        ]);
    }

    public function rejected(array $params): void
    {
        ExportService::rejectedRows((int)($params['id'] ?? 0));
    }

    public function rollback(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $db = Database::i();
        $b = $db->one('SELECT * FROM import_batches WHERE id = :id', ['id' => $id]);
        if (!$b) Response::abort(404, 'Batch import tidak ditemukan.');
        if ($b['status'] === 'rolled_back') { Flash::warning('Batch ini sudah dibatalkan sebelumnya.'); $this->redirect('/import/' . $id); }
        try {
            $res = ImportService::rollback($id);
            Audit::log('import', 'rollback', 'import_batch', $id,
                'Import ' . $b['batch_no'] . ' dibatalkan: ' . $res['invoice'] . ' invoice, '
                . $res['pembayaran'] . ' pembayaran, ' . $res['revisi'] . ' revisi dikembalikan');
            Flash::success('Import dibatalkan: ' . $res['invoice'] . ' invoice, ' . $res['pembayaran']
                . ' pembayaran, dan ' . $res['revisi'] . ' revisi telah dikeluarkan dari sistem.');
        } catch (\Throwable $e) {
            Flash::error('Pembatalan import gagal: ' . $e->getMessage());
        }
        $this->redirect('/import/' . $id);
    }
}
