<?php

namespace App\Modules\Accounting\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Responses\ApiResponse;
use App\Modules\Accounting\Models\AccountTransaction;
use App\Modules\Accounting\Models\Expense;
use App\Modules\Accounting\Models\ExpenseCategory;
use App\Modules\Accounting\Models\FinancialAccount;
use App\Modules\Accounting\Requests\StoreExpenseRequest;
use App\Modules\Accounting\Requests\UpdateExpenseRequest;
use App\Modules\Accounting\Resources\ExpenseResource;
use App\Modules\Invoice\Models\Payment;
use App\Modules\POS\Models\Sale;
use App\Modules\Tenant\Models\Company;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class ExpenseController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $query = $this->filteredExpensesQuery($request, $company);

        if ($request->hasAny(['page', 'per_page'])) {
            $perPage = min(max((int) $request->query('per_page', 15), 1), 100);
            $paginator = $query->paginate($perPage);

            return ApiResponse::success([
                'data' => ExpenseResource::collection($paginator->items()),
                'meta' => [
                    'current_page' => $paginator->currentPage(),
                    'from' => $paginator->firstItem(),
                    'last_page' => $paginator->lastPage(),
                    'per_page' => $paginator->perPage(),
                    'to' => $paginator->lastItem(),
                    'total' => $paginator->total(),
                ],
            ]);
        }

        return ApiResponse::success(ExpenseResource::collection($query->get()));
    }

    public function store(StoreExpenseRequest $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $data = $request->validated();

        if (! empty($data['expense_category_id']) && ! $this->categoryBelongsToCompany($company, (int) $data['expense_category_id'])) {
            return ApiResponse::error('Expense category not found', 404);
        }

        if (! empty($data['financial_account_id']) && ! $this->accountBelongsToCompany($company, (int) $data['financial_account_id'])) {
            return ApiResponse::error('Financial account not found', 404);
        }

        $expense = Expense::query()->create([
            'company_id' => $company->id,
            'expense_category_id' => $data['expense_category_id'] ?? null,
            'financial_account_id' => $data['financial_account_id'] ?? null,
            'expense_number' => $this->nextExpenseNumber($company),
            'expense_date' => $data['expense_date'],
            'description' => $data['description'],
            'amount' => $data['amount'],
            'payment_method' => $data['payment_method'] ?? 'cash',
            'vendor' => $data['vendor'] ?? null,
            'reference' => $data['reference'] ?? null,
            'notes' => $data['notes'] ?? null,
            'status' => $data['status'] ?? 'recorded',
        ]);

        $this->syncExpenseTransaction($expense);

        return ApiResponse::success(
            new ExpenseResource($expense->load(['category', 'financialAccount'])),
            'Expense recorded successfully',
            201
        );
    }

    public function update(UpdateExpenseRequest $request, Expense $expense): JsonResponse
    {
        $company = $this->currentCompany($request);

        if ((int) $expense->company_id !== (int) $company->id) {
            return ApiResponse::error('Expense not found', 404);
        }

        $data = $request->validated();

        if (! empty($data['expense_category_id']) && ! $this->categoryBelongsToCompany($company, (int) $data['expense_category_id'])) {
            return ApiResponse::error('Expense category not found', 404);
        }

        if (! empty($data['financial_account_id']) && ! $this->accountBelongsToCompany($company, (int) $data['financial_account_id'])) {
            return ApiResponse::error('Financial account not found', 404);
        }

        $expense->update($data);
        $this->syncExpenseTransaction($expense->fresh());

        return ApiResponse::success(
            new ExpenseResource($expense->fresh()->load(['category', 'financialAccount'])),
            'Expense updated successfully'
        );
    }

    public function exportCsv(Request $request): Response
    {
        $company = $this->currentCompany($request);
        $expenses = $this->filteredExpensesQuery($request, $company)->get();

        $handle = fopen('php://temp', 'r+');

        fputcsv($handle, [
            'Expense Number',
            'Date',
            'Category',
            'Account',
            'Description',
            'Amount',
            'Payment Method',
            'Vendor',
            'Reference',
            'Status',
            'Notes',
        ]);

        foreach ($expenses as $expense) {
            fputcsv($handle, [
                $expense->expense_number,
                optional($expense->expense_date)->format('Y-m-d'),
                $expense->category?->name,
                $expense->financialAccount?->name,
                $expense->description,
                $expense->amount,
                $expense->payment_method,
                $expense->vendor,
                $expense->reference,
                $expense->status,
                $expense->notes,
            ]);
        }

        rewind($handle);
        $csv = stream_get_contents($handle);
        fclose($handle);

        return response($csv, 200, [
            'Content-Type' => 'text/csv; charset=UTF-8',
            'Content-Disposition' => 'attachment; filename="accounting-expenses.csv"',
        ]);
    }

    public function summary(Request $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        [$startDate, $endDate] = $this->dateRange($request);

        $expenseQuery = Expense::query()
            ->where('company_id', $company->id)
            ->where('status', 'recorded')
            ->whereBetween('expense_date', [$startDate, $endDate]);
        $paymentQuery = Payment::query()
            ->where('company_id', $company->id)
            ->whereBetween('paid_at', [$startDate, $endDate]);
        $posSalesQuery = Sale::query()
            ->where('company_id', $company->id)
            ->where('status', 'completed')
            ->whereBetween('sale_date', [$startDate, $endDate]);

        $invoiceIncome = (float) (clone $paymentQuery)->sum('amount');
        $posIncome = (float) (clone $posSalesQuery)->sum('paid_total');
        $totalExpenses = (float) (clone $expenseQuery)->sum('amount');
        $totalIncome = $invoiceIncome + $posIncome;

        $byCategory = Expense::query()
            ->where('expenses.company_id', $company->id)
            ->where('expenses.status', 'recorded')
            ->whereBetween('expenses.expense_date', [$startDate, $endDate])
            ->leftJoin('expense_categories', 'expenses.expense_category_id', '=', 'expense_categories.id')
            ->selectRaw('COALESCE(expense_categories.name, ?) as category, COUNT(*) as count, COALESCE(SUM(expenses.amount), 0) as total', ['Uncategorized'])
            ->groupBy('expense_categories.name')
            ->orderByDesc('total')
            ->get()
            ->map(fn ($row): array => [
                'category' => $row->category,
                'count' => (int) $row->count,
                'total' => $this->money($row->total),
            ]);

        return ApiResponse::success([
            'period' => [
                'start_date' => $startDate,
                'end_date' => $endDate,
            ],
            'income' => [
                'invoice_payments' => $this->money($invoiceIncome),
                'pos_sales' => $this->money($posIncome),
                'total' => $this->money($totalIncome),
            ],
            'expenses' => [
                'count' => (clone $expenseQuery)->count(),
                'total' => $this->money($totalExpenses),
                'by_category' => $byCategory,
            ],
            'net_profit' => $this->money($totalIncome - $totalExpenses),
        ]);
    }

    private function nextExpenseNumber(Company $company): string
    {
        $next = Expense::query()
            ->where('company_id', $company->id)
            ->count() + 1;

        return $this->settingPrefix($company, 'expense_prefix', 'EXP').'-'.now()->format('Y').'-'.str_pad((string) $next, 6, '0', STR_PAD_LEFT);
    }

    private function settingPrefix(Company $company, string $key, string $default): string
    {
        $prefix = strtoupper((string) ($company->settings[$key] ?? $default));

        return $prefix !== '' ? $prefix : $default;
    }

    /**
     * @return array{0: string, 1: string}
     */
    private function dateRange(Request $request): array
    {
        $startDate = $request->query('start_date')
            ? CarbonImmutable::parse((string) $request->query('start_date'))->toDateString()
            : now()->startOfMonth()->toDateString();
        $endDate = $request->query('end_date')
            ? CarbonImmutable::parse((string) $request->query('end_date'))->toDateString()
            : now()->endOfMonth()->toDateString();

        return [$startDate, $endDate];
    }

    private function money(mixed $value): string
    {
        return number_format((float) $value, 2, '.', '');
    }

    private function currentCompany(Request $request): Company
    {
        return $request->attributes->get('currentCompany');
    }

    private function categoryBelongsToCompany(Company $company, int $categoryId): bool
    {
        return ExpenseCategory::query()
            ->where('company_id', $company->id)
            ->whereKey($categoryId)
            ->exists();
    }

    private function accountBelongsToCompany(Company $company, int $accountId): bool
    {
        return FinancialAccount::query()
            ->where('company_id', $company->id)
            ->whereKey($accountId)
            ->exists();
    }

    private function syncExpenseTransaction(Expense $expense): void
    {
        AccountTransaction::query()
            ->where('source_type', Expense::class)
            ->where('source_id', $expense->id)
            ->delete();

        if ($expense->status !== 'recorded' || ! $expense->financial_account_id) {
            return;
        }

        AccountTransaction::query()->create([
            'company_id' => $expense->company_id,
            'financial_account_id' => $expense->financial_account_id,
            'source_type' => Expense::class,
            'source_id' => $expense->id,
            'transaction_date' => $expense->expense_date,
            'type' => 'expense',
            'direction' => 'outflow',
            'amount' => $expense->amount,
            'description' => $expense->description,
            'reference' => $expense->expense_number,
            'notes' => $expense->notes,
        ]);
    }

    /**
     * @return Builder<Expense>
     */
    private function filteredExpensesQuery(Request $request, Company $company): Builder
    {
        return Expense::query()
            ->where('company_id', $company->id)
            ->with(['category', 'financialAccount'])
            ->when($request->filled('search'), function ($query) use ($request): void {
                $search = $request->string('search')->toString();

                $query->where(function ($query) use ($search): void {
                    $query->where('expense_number', 'like', "%{$search}%")
                        ->orWhere('description', 'like', "%{$search}%")
                        ->orWhere('vendor', 'like', "%{$search}%")
                        ->orWhere('reference', 'like', "%{$search}%");
                });
            })
            ->when($request->filled('expense_category_id'), function ($query) use ($request): void {
                $query->where('expense_category_id', $request->integer('expense_category_id'));
            })
            ->when($request->filled('financial_account_id'), function ($query) use ($request): void {
                $query->where('financial_account_id', $request->integer('financial_account_id'));
            })
            ->when($request->filled('payment_method'), function ($query) use ($request): void {
                $query->where('payment_method', $request->string('payment_method')->toString());
            })
            ->when($request->filled('status'), function ($query) use ($request): void {
                $query->where('status', $request->string('status')->toString());
            })
            ->when($request->filled('date_from'), function ($query) use ($request): void {
                $query->whereDate('expense_date', '>=', $request->date('date_from'));
            })
            ->when($request->filled('date_to'), function ($query) use ($request): void {
                $query->whereDate('expense_date', '<=', $request->date('date_to'));
            })
            ->latest('expense_date')
            ->latest();
    }
}
