<?php

namespace App\Http\Services;

use App\Models\Budget;
use App\Enums\UserType;
use App\Models\Payment;
use App\Models\UserBank;
use App\Enums\BudgetType;
use App\Enums\ModuleType;
use App\Enums\UserBankType;
use App\Models\BudgetDetail;
use App\Models\BudgetCategory;
use App\Enums\ActionStatusType;
use App\Enums\BudgetStatusType;
use Illuminate\Support\Facades\DB;
use App\Http\Services\ApprovalLogService;

class BudgetApprovalService
{
  public function approve($request, Budget $budget)
  {
    $user = auth()->user();
    $data = $request->input('budget_categories', []);

    $statusMap = collect([
      'is_approved' => BudgetStatusType::Approved,
      'is_rejected' => BudgetStatusType::Rejected,
      'is_revision' => BudgetStatusType::Revision,
    ]);

    $budgetDetails = collect($data)->map(function ($categoryData) use ($statusMap) {
      return collect($categoryData['budget_details'])->map(function ($budgetDetails) use ($statusMap) {
        $status = $statusMap->first(fn($status, $key) => !empty($budgetDetails[$key])) ?? BudgetStatusType::Review;

        return [
          "id" => $budgetDetails["id"],
          "status_message" => $budgetDetails["status_message"],
          "status" => $status,
        ];
      });
    });

    $budgetDetails = $budgetDetails->flatten(1)->unique('id');
    $ids = $budgetDetails->pluck('id')->toArray();
    $existingDetails = BudgetDetail::whereIn('id', $ids)->get()->keyBy('id');

    $approvalLog = $budget->latestApprovalLog;
    $allowedIds = [];
    if ($approvalLog && $approvalLog->children()->exists()) {
      $allowedIds = $approvalLog->children()->pluck('budget_detail_id')->toArray();
    }

    $upsertData = [];
    foreach ($budgetDetails as $item) {
      if (!empty($allowedIds) && !in_array($item['id'], $allowedIds)) {
        continue;
      }

      if (isset($existingDetails[$item['id']])) {
        $detail = $existingDetails[$item['id']];
        $detail->status = $item['status'];
        $detail->status_message = $item['status_message'];
        $detail->updated_at = now();

        $detail->user_review_id = $user->id;
        $detail->review_at = now();
        $upsertData[] = $detail->getAttributes();
      }
    }

    BudgetDetail::upsert(
      $upsertData,
      ['id'],
      ['status', 'status_message', 'updated_at']
    );

    $budgetCategories = BudgetCategory::with('budgetDetails')->where('budget_id', $budget->id)->get();
    foreach ($budgetCategories as $budgetCategory) {
      $budgetCategory->price_amount_total = $budgetCategory->budgetDetails
        ->where('status', '!=', BudgetStatusType::Rejected)
        ->sum('price_total');
      $budgetCategory->save();
    }

    $budget->debit_amount = $budget->budgetCategories()->sum('price_amount_total');

    $upsertData = collect($upsertData);
    $this->createItemLogs($request, $upsertData, BudgetStatusType::Revision);
    $this->createItemLogs($request, $upsertData, BudgetStatusType::Rejected);

    $notApproved = $upsertData->whereIn('status', [BudgetStatusType::Revision, BudgetStatusType::Rejected])->count();
    $budget->status = $notApproved > 0 ? BudgetStatusType::fromValue(BudgetStatusType::Revision) : BudgetStatusType::fromValue(BudgetStatusType::Approved);
    $budget->review_at = now();

    $detail = null;
    if (in_array($budget->status, [BudgetStatusType::Revision, BudgetStatusType::Rejected])) {
      $budgetDetail = BudgetDetail::with("quantityUnits.unitType")
        ->where('budget_id', $budget->id)
        ->whereIn('status', [BudgetStatusType::Revision, BudgetStatusType::Rejected])
        ->get();
      $detail = json_encode($budgetDetail);
    }

    $minAmountForDirectorApproval = 50_000_000;
    $isDirectorApprovalNeeded = $budget->status == BudgetStatusType::Approved && $user->role != UserType::Director && $budget->remaining_amount > $minAmountForDirectorApproval;

    (new ApprovalLogService())->create($request, $budget, ModuleType::Budget, null, [
      'note' => $request->custom_note,
      'detail' => $detail,
      'is_notify' => !$isDirectorApprovalNeeded,
    ]);

    if ($isDirectorApprovalNeeded) {
      $budget->status = BudgetStatusType::fromValue(BudgetStatusType::Review);
      (new NotificationService())->createInfo($budget, UserType::User, $budget->department_id, $user->name);

      (new ApprovalLogService())->create($request, $budget, ModuleType::Budget, null, [
        'note' => $request->custom_note,
      ]);
    }

    $budget->review_at = now();
    $budget->user_review_id = $user->id;
    $budget->save();

    if ($budget->status == BudgetStatusType::Approved) {
      if ($budget->type == BudgetType::CashAdvance) {
        $payment = $this->createPayment($budget);
        (new ApprovalLogService())->create($request, $payment, ModuleType::Payment, ActionStatusType::Submit, [
          'sender_user_id' => $budget->user_id,
          'is_finance' => true,
          'target_finance_department' => true,
        ]);
      }

      if (!$isDirectorApprovalNeeded && in_array(auth()->user()->role, [UserType::Manager])) {
        (new NotificationService())->createInfo($budget, UserType::Director, $budget->department_id, $user->name);
      }

      (new BalanceService)->syncBalance($budget->id);
    }

    return $budget;
  }

  private function createItemLogs($request, $upsertData, $status)
  {
    $filteredDetails = $upsertData->whereIn('status', [$status]);
    $ids = $filteredDetails->pluck('id')->toArray();
    $models = BudgetDetail::with('quantityUnits.unitType')->whereIn('id', $ids)->get()->keyBy('id');

    foreach ($filteredDetails as $detail) {
      $detail = (object) $detail;
      if ($detailModel = $models->get($detail->id)) {
        (new ApprovalLogService())->create($request, $detailModel, ModuleType::BudgetDetail, null, [
          'budget_detail_id' => $detailModel->id,
          'note' => $detail->status_message,
          'detail' => json_encode($detailModel)
        ]);
      }
    }
  }

  private function createPayment(Budget $budget)
  {
    $userBank = UserBank::where('user_id', $budget->user_id)->where('type', UserBankType::Personal)->first();
    $lastPayment = DB::table("payments")->select("code")->where('budget_id', $budget->id)->latest("id")->whereNotNull('code')->first();
    $code = "INV01";

    if ($lastPayment) {
      $lastCode = explode("/", $lastPayment->code);
      $lastCode = $lastCode[0];

      if ($lastCode) {
        $lastCode = str_replace("INV", "", $lastCode);
        $lastCode = str_pad(intval($lastCode) + 1, 2, "0", STR_PAD_LEFT);
        $code = "INV{$lastCode}";
      }
    }

    $code .= "/{$budget->code}";

    $payment = Payment::create([
      "budget_id" => $budget->id,
      "department_id" => $budget->department_id,
      "company_id" => $budget->company_id,
      "user_bank_id" => $userBank?->id ?? null,
      "code" => $code,
      "amount" => $budget->remaining_amount,
      "description" => $budget->name,
      "method" => null,
      "type" => null,
      'payment_date' => now()->toDateTimeString(),
      'due_date' => now()->toDateTimeString(),
    ]);

    return $payment;
  }
}
