<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\ExpenseCategory;
use App\Models\ExpenseReminder;
use App\Models\FinancialAccount;
use App\Models\RecurringExpense;
use App\Services\AuditService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class RecurringExpenseController extends Controller
{
    public function index(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        $schedules = RecurringExpense::query()->with(['branch','category','financialAccount','creator'])
            ->whereIn('branch_id',$branchIds)->latest('id')->paginate(20,['*'],'schedule_page')->withQueryString();
        $reminders = ExpenseReminder::query()->with(['recurringExpense.category','expense','branch','acknowledger'])
            ->whereIn('branch_id',$branchIds)->latest('due_date')->paginate(20,['*'],'reminder_page')->withQueryString();
        return view('recurring-expenses.index', compact('schedules','reminders'));
    }

    public function create(Request $request): View
    {
        [$branches,$categories,$accounts] = $this->formData($request);
        return view('recurring-expenses.create', compact('branches','categories','accounts'));
    }

    public function store(Request $request): RedirectResponse
    {
        $data = $this->validateData($request, false);
        $this->assertBranchAccess($request,(int)$data['branch_id']);
        $branch = Branch::findOrFail($data['branch_id']);
        $this->assertSelections($branch,$data);

        $schedule = RecurringExpense::create([
            'business_id'=>$branch->business_id,
            'branch_id'=>$branch->id,
            'expense_category_id'=>$data['expense_category_id'],
            'financial_account_id'=>$data['financial_account_id'],
            'description'=>$data['description'],
            'amount'=>$data['amount'],
            'supplier'=>$data['supplier'] ?? null,
            'start_date'=>$data['start_date'],
            'next_due_date'=>$data['start_date'],
            'end_date'=>$data['end_date'] ?? null,
            'recurrence'=>$data['recurrence'],
            'interval_days'=>$data['recurrence']==='custom_days' ? $data['interval_days'] : null,
            'reminder_days_before'=>$data['reminder_days_before'],
            'status'=>$data['status'],
            'notes'=>$data['notes'] ?? null,
            'created_by'=>$request->user()->id,
            'updated_by'=>$request->user()->id,
        ]);
        AuditService::record('recurring_expense.created',$schedule,[], $schedule->toArray(),$schedule->branch_id);
        return redirect()->route('recurring-expenses.index')->with('success','Recurring expense schedule created.');
    }

    public function edit(Request $request, RecurringExpense $recurringExpense): View
    {
        $this->assertBranchAccess($request,$recurringExpense->branch_id);
        [$branches,$categories,$accounts] = $this->formData($request);
        return view('recurring-expenses.edit', compact('recurringExpense','branches','categories','accounts'));
    }

    public function update(Request $request, RecurringExpense $recurringExpense): RedirectResponse
    {
        $this->assertBranchAccess($request,$recurringExpense->branch_id);
        $data = $this->validateData($request, true);
        $this->assertBranchAccess($request,(int)$data['branch_id']);
        $branch = Branch::findOrFail($data['branch_id']);
        $this->assertSelections($branch,$data);

        $before = $recurringExpense->only([
            'branch_id','expense_category_id','financial_account_id','description','amount','supplier','next_due_date','end_date',
            'recurrence','interval_days','reminder_days_before','status','notes',
        ]);
        $recurringExpense->update([
            'business_id'=>$branch->business_id,
            'branch_id'=>$branch->id,
            'expense_category_id'=>$data['expense_category_id'],
            'financial_account_id'=>$data['financial_account_id'],
            'description'=>$data['description'],
            'amount'=>$data['amount'],
            'supplier'=>$data['supplier'] ?? null,
            'next_due_date'=>$data['next_due_date'],
            'end_date'=>$data['end_date'] ?? null,
            'recurrence'=>$data['recurrence'],
            'interval_days'=>$data['recurrence']==='custom_days' ? $data['interval_days'] : null,
            'reminder_days_before'=>$data['reminder_days_before'],
            'status'=>$data['status'],
            'notes'=>$data['notes'] ?? null,
            'updated_by'=>$request->user()->id,
        ]);
        AuditService::record('recurring_expense.updated',$recurringExpense,$before,$recurringExpense->only(array_keys($before)),$recurringExpense->branch_id);
        return redirect()->route('recurring-expenses.index')->with('success','Recurring expense schedule updated.');
    }

    public function acknowledge(Request $request, ExpenseReminder $reminder): RedirectResponse
    {
        $this->assertBranchAccess($request,$reminder->branch_id);
        if ($reminder->status !== 'acknowledged') {
            $reminder->update(['status'=>'acknowledged','acknowledged_by'=>$request->user()->id,'acknowledged_at'=>now()]);
            AuditService::record('expense_reminder.acknowledged',$reminder,[],[
                'recurring_expense_id'=>$reminder->recurring_expense_id,'due_date'=>$reminder->due_date?->toDateString(),
            ],$reminder->branch_id);
        }
        return back()->with('success','Expense reminder acknowledged.');
    }

    private function validateData(Request $request, bool $editing): array
    {
        $rules = [
            'branch_id'=>['required','integer','exists:branches,id'],
            'expense_category_id'=>['required','integer','exists:expense_categories,id'],
            'financial_account_id'=>['required','integer','exists:financial_accounts,id'],
            'description'=>['required','string','max:255'],
            'amount'=>['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'supplier'=>['nullable','string','max:180'],
            'end_date'=>['nullable','date'],
            'recurrence'=>['required',Rule::in(['weekly','monthly','quarterly','yearly','custom_days'])],
            'interval_days'=>['nullable','required_if:recurrence,custom_days','integer','min:1','max:3650'],
            'reminder_days_before'=>['required','integer','min:0','max:365'],
            'status'=>['required',Rule::in(['active','inactive'])],
            'notes'=>['nullable','string','max:3000'],
        ];
        if ($editing) {
            $rules['next_due_date']=['required','date'];
            $rules['end_date'][]='after_or_equal:next_due_date';
        } else {
            $rules['start_date']=['required','date'];
            $rules['end_date'][]='after_or_equal:start_date';
        }
        return $request->validate($rules);
    }

    private function assertSelections(Branch $branch, array $data): void
    {
        $category = ExpenseCategory::findOrFail($data['expense_category_id']);
        $account = FinancialAccount::findOrFail($data['financial_account_id']);
        abort_unless($category->business_id===$branch->business_id && $category->status==='active',422,'The expense category is invalid for this branch.');
        abort_unless($account->branch_id===$branch->id && $account->status==='active',422,'The financial account is invalid for this branch.');
    }

    private function formData(Request $request): array
    {
        $branchIds=$this->allowedBranchIds($request);
        $branches=Branch::whereIn('id',$branchIds)->where('status','active')->orderBy('name')->get();
        $businessIds=$branches->pluck('business_id')->unique();
        $categories=ExpenseCategory::whereIn('business_id',$businessIds)->where('status','active')->orderBy('name')->get();
        $accounts=FinancialAccount::whereIn('branch_id',$branchIds)->where('status','active')->orderBy('name')->get();
        return [$branches,$categories,$accounts];
    }

    private function allowedBranchIds(Request $request)
    {
        return collect(app(\App\Services\BranchAccessService::class)->accessibleBranchIds($request->user()));
    }

    private function assertBranchAccess(Request $request, int $branchId): void
    {
        abort_unless($request->user()->canAccessBranch($branchId),403);
    }
}
