<?php
namespace App\Http\Controllers;
use App\Models as M;
use App\Services\FinanceService;
use App\Services\SequenceService;
use App\Support\CrudRegistry;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class JournalController extends Controller {
    public function index(Request $r) { return app(CrudController::class)->index($r, 'journals'); }
    public function show(int $id) { return app(CrudController::class)->show('journals', $id); }
    public function create() {
        $this->need('finance.create');
        return view('docs.journal.form', $this->formData(new M\Journal(['journal_date' => date('Y-m-d')])));
    }
    public function store(Request $r) {
        $this->need('finance.create');
        $d = $this->validated($r);
        $j = DB::transaction(function () use ($d) {
            $j = M\Journal::create(['journal_no' => SequenceService::next('journal'), 'journal_date' => $d['journal_date'],
                'fiscal_year_id' => M\FiscalYear::current()?->id, 'reference' => $d['reference'] ?? null, 'narration' => $d['narration'],
                'status' => 'draft', 'source_module' => 'manual', 'created_by' => Auth::id(), 'total_debit' => 0, 'total_credit' => 0]);
            $this->saveLines($j, $d['lines']);
            return $j;
        });
        return redirect()->route('journals.show', $j)->with('success', 'Journal '.$j->journal_no.' drafted (must balance to post).');
    }
    public function edit(int $id) {
        $this->need('finance.edit');
        $j = M\Journal::with('lines')->findOrFail($id);
        abort_unless($j->status === 'draft', 403, 'Posted journals cannot be edited — reverse instead.');
        return view('docs.journal.form', $this->formData($j));
    }
    public function update(Request $r, int $id) {
        $this->need('finance.edit');
        $j = M\Journal::findOrFail($id);
        abort_unless($j->status === 'draft', 403, 'Posted journals cannot be edited.');
        $d = $this->validated($r);
        DB::transaction(function () use ($j, $d) {
            $j->update(['journal_date' => $d['journal_date'], 'reference' => $d['reference'] ?? null, 'narration' => $d['narration']]);
            $j->lines()->delete();
            $this->saveLines($j, $d['lines']);
        });
        return redirect()->route('journals.show', $j)->with('success', 'Journal updated.');
    }
    public function destroy(int $id) {
        $this->need('finance.delete');
        $j = M\Journal::findOrFail($id);
        abort_unless($j->status === 'draft', 403, 'Posted journals cannot be deleted — reverse instead.');
        $j->delete();
        return redirect()->route('journals.index')->with('success', 'Journal deleted.');
    }
    public function post(int $id) {
        $this->need('finance.approve');
        $j = M\Journal::with('lines.account')->findOrFail($id);
        abort_unless($j->status === 'draft', 403, 'Already posted.');
        abort_if(abs($j->total_debit - $j->total_credit) > 0.009, 403, 'Unbalanced journal cannot post.');
        DB::transaction(function () use ($j) {
            foreach ($j->lines as $l) FinanceService::applyBalance($l->account, $l->debit, $l->credit);
            $j->update(['status' => 'posted', 'posted_by' => Auth::id(), 'posted_at' => now()]);
        });
        return back()->with('success', 'Journal posted to general ledger.');
    }
    public function reverse(int $id) {
        $this->need('finance.approve');
        $j = M\Journal::with('lines')->findOrFail($id);
        abort_unless($j->status === 'posted', 403, 'Only posted journals can reverse.');
        $rev = FinanceService::reverseJournal($j, 'Manual reversal');
        return redirect()->route('journals.show', $rev)->with('success', 'Reversal journal '.$rev->journal_no.' posted.');
    }
    public function print(Request $r, int $id) {
        $this->need('finance.view');
        $doc = M\Journal::with('lines.account')->findOrFail($id);
        if ($r->get('pdf')) return \App\Services\PdfService::download('docs.journal.print', ['j' => $doc], 'JV-'.($doc->journal_no ?? $id).'.pdf');
        return view('docs.journal.print', ['j' => $doc]);
    }
    protected function validated(Request $r): array {
        $d = $r->validate(['journal_date' => 'required|date', 'narration' => 'required|max:255', 'reference' => 'nullable|max:80',
            'lines' => 'required|array|min:2', 'lines.*.account_id' => 'required|exists:accounts,id', 'lines.*.debit' => 'nullable|numeric|min:0',
            'lines.*.credit' => 'nullable|numeric|min:0', 'lines.*.narration' => 'nullable|max:255']);
        return $d;
    }
    protected function saveLines(M\Journal $j, array $lines): void {
        $td = 0; $tc = 0;
        foreach ($lines as $l) {
            $dr = round($l['debit'] ?? 0, 2); $cr = round($l['credit'] ?? 0, 2);
            if ($dr == 0 && $cr == 0) continue;
            $j->lines()->create(['account_id' => $l['account_id'], 'debit' => $dr, 'credit' => $cr, 'narration' => $l['narration'] ?? $j->narration]);
            $td += $dr; $tc += $cr;
        }
        $j->update(['total_debit' => round($td, 2), 'total_credit' => round($tc, 2)]);
    }
    protected function formData(M\Journal $j): array {
        return ['record' => $j, 'isNew' => !$j->exists, 'cfg' => CrudRegistry::get('journals'), 'key' => 'journals',
            'docNo' => $j->exists ? $j->journal_no : SequenceService::peek('journal'),
            'accounts' => M\Account::where('is_active', true)->orderBy('code')->get()];
    }
}
