<?php

namespace App\Http\Controllers\Accounting;

use App\Enums\DocumentStatus;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\Branch;
use App\Models\CostCenter;
use App\Models\Department;
use App\Models\JournalEntry;
use App\Models\ManualJournalVoucher;
use App\Models\Party;
use App\Services\Accounting\ManualJournalPostingService;
use App\Services\Accounting\ReversalService;
use App\Services\Documents\DocumentNumberService;
use App\Services\Workflow\ApprovalWorkflowService;
use App\Support\Decimal;
use DomainException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Throwable;

final class JournalController extends Controller
{
    public function __construct(private readonly DocumentNumberService $numbers,private readonly ApprovalWorkflowService $workflow,private readonly ManualJournalPostingService $posting,private readonly ReversalService $reversals){}
    public function index(Request $r):View{$this->permission($r,'view');$q=JournalEntry::with(['branch','lines'])->where('company_id',$r->user()->company_id);$this->scope($r,$q);if($s=trim((string)$r->query('search')))$q->where(fn($q)=>$q->where('journal_number','like',"%$s%")->orWhere('reference_number','like',"%$s%")->orWhere('description','like',"%$s%"));if($from=$r->query('from'))$q->whereDate('posting_date','>=',$from);if($to=$r->query('to'))$q->whereDate('posting_date','<=',$to);$journals=$q->latest('posting_date')->latest('id')->paginate(25)->withQueryString();$drafts=ManualJournalVoucher::where('company_id',$r->user()->company_id)->whereNotIn('status',['posted'])->latest()->limit(10)->get();return view('journals.index',compact('journals','drafts'));}
    public function show(Request $r,JournalEntry $journal):View{$this->accessJournal($r,$journal,'view');$journal->load(['branch','period','lines.account','lines.party','source','reversalOf']);$reversal=JournalEntry::where('reversal_of_id',$journal->id)->first();return view('journals.show',compact('journal','reversal'));}
    public function create(Request $r):View{$this->permission($r,'create');$voucher=new ManualJournalVoucher(['branch_id'=>$r->user()->branch_id,'posting_date'=>today(),'document_date'=>today(),'currency_code'=>'AED','exchange_rate'=>'1','status'=>DocumentStatus::DRAFT]);$voucher->setRelation('lines',collect());return $this->form($r,$voucher);}
    public function store(Request $r):RedirectResponse{$this->permission($r,'create');[$d,$lines]=$this->validated($r);$d+=['company_id'=>$r->user()->company_id,'created_by'=>$r->user()->id,'status'=>DocumentStatus::DRAFT];$d['voucher_number']=$this->numbers->next((int)$r->user()->company_id,(int)$d['branch_id'],'MJV',$d['posting_date']);$v=DB::transaction(function()use($d,$lines){$v=ManualJournalVoucher::create($d);foreach($lines as $i=>$line)$v->lines()->create(['line_number'=>$i+1,...$line]);return$v;});return redirect()->route('journals.vouchers.show',$v)->with('success','Manual journal draft created.');}
    public function voucherShow(Request $r,ManualJournalVoucher $voucher):View{$this->accessVoucher($r,$voucher,'view');$voucher->load(['branch','lines.account','lines.party','journal.lines.account']);return view('journals.voucher-show',compact('voucher'));}
    public function edit(Request $r,ManualJournalVoucher $voucher):View{$this->accessVoucher($r,$voucher,'edit_draft');$this->editable($voucher);return$this->form($r,$voucher->load('lines'));}
    public function update(Request $r,ManualJournalVoucher $voucher):RedirectResponse{$this->accessVoucher($r,$voucher,'edit_draft');$this->editable($voucher);[$d,$lines]=$this->validated($r);DB::transaction(function()use($voucher,$d,$lines){$voucher->update($d+['status'=>DocumentStatus::DRAFT,'approved_by'=>null,'approved_at'=>null]);$voucher->lines()->delete();foreach($lines as $i=>$line)$voucher->lines()->create(['line_number'=>$i+1,...$line]);});return redirect()->route('journals.vouchers.show',$voucher)->with('success','Manual journal updated.');}
    public function destroy(Request $r,ManualJournalVoucher $voucher):RedirectResponse{$this->accessVoucher($r,$voucher,'delete_draft');$this->editable($voucher);$voucher->delete();return redirect()->route('journals.index')->with('success','Manual journal draft deleted.');}
    public function submit(Request $r,ManualJournalVoucher $v):RedirectResponse{return$this->transition($r,$v,'submit',fn()=>$this->workflow->submit($v),'Journal submitted.');}public function approve(Request $r,ManualJournalVoucher $v):RedirectResponse{return$this->transition($r,$v,'approve',fn()=>$this->workflow->approve($v),'Journal approved.');}public function reject(Request $r,ManualJournalVoucher $v):RedirectResponse{$r->validate(['reason'=>'required|string|max:1000']);return$this->transition($r,$v,'reject',fn()=>$this->workflow->reject($v,$r->string('reason')->toString()),'Journal rejected.');}public function post(Request $r,ManualJournalVoucher $v):RedirectResponse{return$this->transition($r,$v,'post',fn()=>$this->posting->post($v),'Journal posted.');}
    public function reverse(Request $r,JournalEntry $journal):RedirectResponse{$this->accessJournal($r,$journal,'reverse');$d=$r->validate(['reversal_date'=>'required|date','reason'=>'required|string|max:2000']);try{$rev=$this->reversals->reverse($journal,$d['reversal_date'],$d['reason']);}catch(Throwable $e){return back()->with('error',$e->getMessage());}return redirect()->route('journals.show',$rev)->with('success','Reversal journal posted.');}
    private function transition(Request $r,ManualJournalVoucher $v,string $a,callable $fn,string $m):RedirectResponse{$this->accessVoucher($r,$v,$a);try{$fn();}catch(Throwable $e){return back()->with('error',$e->getMessage());}return back()->with('success',$m);}
    private function form(Request $r,ManualJournalVoucher $voucher):View{$c=$r->user()->company_id;$branches=Branch::where('company_id',$c)->where('is_active',true)->orderBy('code')->get();$accounts=Account::where('company_id',$c)->where('is_active',true)->orderBy('code')->get();$parties=Party::where('company_id',$c)->where('is_active',true)->orderBy('legal_name')->get();$departments=Department::where('company_id',$c)->orderBy('name')->get();$costCenters=CostCenter::where('company_id',$c)->orderBy('name')->get();return view('journals.form',compact('voucher','branches','accounts','parties','departments','costCenters'));}
    private function validated(Request $r):array{$d=$r->validate(['branch_id'=>'required|integer','posting_date'=>'required|date','document_date'=>'required|date','reference_number'=>'nullable|string|max:150','description'=>'required|string|max:2000','currency_code'=>'required|string|size:3','exchange_rate'=>'required|numeric|gt:0','lines'=>'required|array|min:2','lines.*.account_id'=>'required|integer','lines.*.party_id'=>'nullable|integer','lines.*.party_ledger'=>['nullable',Rule::in(['customer','vendor'])],'lines.*.department_id'=>'nullable|integer','lines.*.cost_center_id'=>'nullable|integer','lines.*.description'=>'nullable|string|max:1000','lines.*.debit'=>'nullable|numeric|min:0','lines.*.credit'=>'nullable|numeric|min:0']);$debit='0';$credit='0';$lines=[];foreach($d['lines'] as $line){$dr=Decimal::scale((string)($line['debit']??0));$cr=Decimal::scale((string)($line['credit']??0));if(Decimal::compare($dr,'0')>0&&Decimal::compare($cr,'0')>0)throw new DomainException('A journal line cannot have both debit and credit.');if(Decimal::compare($dr,'0')===0&&Decimal::compare($cr,'0')===0)continue;$lines[]=$line+['debit'=>$dr,'credit'=>$cr];$debit=Decimal::add($debit,$dr);$credit=Decimal::add($credit,$cr);}if(count($lines)<2||!Decimal::equals($debit,$credit)||Decimal::compare($debit,'0')<=0)throw new DomainException('Journal must contain balanced debit and credit lines.');unset($d['lines']);$d+=['debit_total'=>$debit,'credit_total'=>$credit];return[$d,$lines];}
    private function editable(ManualJournalVoucher $v):void{$s=$v->status instanceof DocumentStatus?$v->status:DocumentStatus::from($v->status);if(!in_array($s,[DocumentStatus::DRAFT,DocumentStatus::REJECTED],true))throw new DomainException('Only draft or rejected journals can be edited.');}private function permission(Request $r,string $a):void{abort_unless($r->user()->hasPermission('journals.'.$a),403);}private function accessJournal(Request $r,JournalEntry $j,string $a):void{$this->permission($r,$a);abort_unless((int)$j->company_id===(int)$r->user()->company_id,404);if($r->user()->branch_id&&!$r->user()->hasPermission('branches.view_all'))abort_unless((int)$j->branch_id===(int)$r->user()->branch_id,403);}private function accessVoucher(Request $r,ManualJournalVoucher $v,string $a):void{$this->permission($r,$a);abort_unless((int)$v->company_id===(int)$r->user()->company_id,404);if($r->user()->branch_id&&!$r->user()->hasPermission('branches.view_all'))abort_unless((int)$v->branch_id===(int)$r->user()->branch_id,403);}private function scope(Request $r,$q):void{if($r->user()->branch_id&&!$r->user()->hasPermission('branches.view_all'))$q->where('branch_id',$r->user()->branch_id);}
}
