<?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\FinancialAccount;
use App\Modules\Accounting\Requests\StoreAccountTransferRequest;
use App\Modules\Accounting\Requests\StoreAccountTransactionRequest;
use App\Modules\Accounting\Resources\AccountTransactionResource;
use App\Modules\Tenant\Models\Company;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;

class AccountTransactionController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $query = $this->filteredTransactionsQuery($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' => AccountTransactionResource::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(AccountTransactionResource::collection($query->get()));
    }

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

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

        fputcsv($handle, [
            'Date',
            'Account',
            'Account Type',
            'Type',
            'Direction',
            'Amount',
            'Description',
            'Reference',
            'Source Type',
            'Source ID',
            'Notes',
        ]);

        foreach ($transactions as $transaction) {
            fputcsv($handle, [
                optional($transaction->transaction_date)->format('Y-m-d'),
                $transaction->financialAccount?->name,
                $transaction->financialAccount?->type,
                $transaction->type,
                $transaction->direction,
                $transaction->amount,
                $transaction->description,
                $transaction->reference,
                $transaction->source_type,
                $transaction->source_id,
                $transaction->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-ledger.csv"',
        ]);
    }

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

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

        $transaction = AccountTransaction::query()->create([
            'company_id' => $company->id,
            'financial_account_id' => $data['financial_account_id'],
            'transaction_date' => $data['transaction_date'],
            'type' => $data['type'],
            'direction' => $data['direction'],
            'amount' => $data['amount'],
            'description' => $data['description'],
            'reference' => $data['reference'] ?? null,
            'notes' => $data['notes'] ?? null,
        ]);

        return ApiResponse::success(
            new AccountTransactionResource($transaction->load('financialAccount')),
            'Account transaction recorded successfully',
            201
        );
    }

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

        $fromAccount = FinancialAccount::query()
            ->where('company_id', $company->id)
            ->find($data['from_account_id']);
        $toAccount = FinancialAccount::query()
            ->where('company_id', $company->id)
            ->find($data['to_account_id']);

        if (! $fromAccount || ! $toAccount) {
            return ApiResponse::error('Financial account not found', 404);
        }

        $reference = $data['reference'] ?? $this->nextTransferReference($company);

        [$outflow, $inflow] = DB::transaction(function () use ($company, $data, $fromAccount, $toAccount, $reference): array {
            $outflow = AccountTransaction::query()->create([
                'company_id' => $company->id,
                'financial_account_id' => $fromAccount->id,
                'transaction_date' => $data['transaction_date'],
                'type' => 'transfer',
                'direction' => 'outflow',
                'amount' => $data['amount'],
                'description' => 'Transfer to '.$toAccount->name.': '.$data['description'],
                'reference' => $reference,
                'notes' => $data['notes'] ?? null,
            ]);

            $inflow = AccountTransaction::query()->create([
                'company_id' => $company->id,
                'financial_account_id' => $toAccount->id,
                'transaction_date' => $data['transaction_date'],
                'type' => 'transfer',
                'direction' => 'inflow',
                'amount' => $data['amount'],
                'description' => 'Transfer from '.$fromAccount->name.': '.$data['description'],
                'reference' => $reference,
                'notes' => $data['notes'] ?? null,
            ]);

            return [$outflow, $inflow];
        });

        return ApiResponse::success([
            'reference' => $reference,
            'transactions' => AccountTransactionResource::collection(collect([
                $outflow->load('financialAccount'),
                $inflow->load('financialAccount'),
            ])),
        ], 'Account transfer recorded successfully', 201);
    }

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

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

    private function nextTransferReference(Company $company): string
    {
        $transferRows = AccountTransaction::query()
            ->where('company_id', $company->id)
            ->where('type', 'transfer')
            ->count();
        $next = (int) floor($transferRows / 2) + 1;

        return 'TRF-'.now()->format('Y').'-'.str_pad((string) $next, 6, '0', STR_PAD_LEFT);
    }

    /**
     * @return Builder<AccountTransaction>
     */
    private function filteredTransactionsQuery(Request $request, Company $company): Builder
    {
        return AccountTransaction::query()
            ->where('company_id', $company->id)
            ->with('financialAccount')
            ->when($request->filled('financial_account_id'), function ($query) use ($request): void {
                $query->where('financial_account_id', $request->integer('financial_account_id'));
            })
            ->when($request->filled('type'), function ($query) use ($request): void {
                $query->where('type', $request->string('type')->toString());
            })
            ->when($request->filled('direction'), function ($query) use ($request): void {
                $query->where('direction', $request->string('direction')->toString());
            })
            ->when($request->filled('date_from'), function ($query) use ($request): void {
                $query->whereDate('transaction_date', '>=', $request->date('date_from'));
            })
            ->when($request->filled('date_to'), function ($query) use ($request): void {
                $query->whereDate('transaction_date', '<=', $request->date('date_to'));
            })
            ->latest('transaction_date')
            ->latest();
    }
}
