<?php

namespace App\Http\Controllers\Api\Finance;

use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Files\FileNameRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchEmailRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchLockRequest;
use App\Http\Requests\Reimbursements\ReimbursementExportBatchRequest;
use App\Http\Requests\Reimbursements\ReimbursementExportRequest;
use App\Http\Requests\Reimbursements\ReimbursementIndexRequest;
use App\Http\Requests\Reimbursements\ReimbursementProcessRequest;
use App\Http\Requests\Reimbursements\ReimbursementUnderProcessingRequest;
use App\Http\Resources\Reimbursements\ReimbursementBatchResource;
use App\Http\Resources\Reimbursements\ReimbursementExpenseResource;
use App\Http\Resources\Reimbursements\ReimbursementRecipientResource;
use App\Http\Resources\Reimbursements\ReimbursementResource;
use App\Http\Resources\Reimbursements\ReimbursementUnderProcessingItemResource;
use App\Models\Reimbursement;
use App\Models\ReimbursementBatch;
use App\Services\Files\FileServeService;
use App\Services\Reimbursements\ReimbursementBatchAssignmentService;
use App\Services\Reimbursements\ReimbursementBatchService;
use App\Services\Reimbursements\ReimbursementContextService;
use App\Services\Reimbursements\ReimbursementCrudService;
use App\Services\Reimbursements\ReimbursementDonationService;
use App\Services\Reimbursements\ReimbursementEmailService;
use App\Services\Reimbursements\ReimbursementExportService;
use App\Services\Reimbursements\ReimbursementFileService;
use App\Services\Reimbursements\ReimbursementQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Validator;
use RuntimeException;

class ReimbursementController extends BaseApiController
{
    public function __construct(
        private ReimbursementContextService $context,
        private ReimbursementQueryService $queryService,
        private ReimbursementDonationService $donationService,
        private ReimbursementBatchService $batchService,
        private ReimbursementBatchAssignmentService $assignmentService,
        private ReimbursementFileService $fileService,
        private ReimbursementExportService $exportService,
        private ReimbursementEmailService $emailService,
        private ReimbursementCrudService $crudService,
        private FileServeService $fileServeService
    ) {
        parent::__construct();
    }

    public function underProcessing(ReimbursementUnderProcessingRequest $request): JsonResponse
    {
        $data = $this->queryService->underProcessing();

        return response()->json([
            'ok' => true,
            'pendingItems' => ReimbursementUnderProcessingItemResource::collection($data['pendingItems'] ?? []),
            'existingBatches' => ReimbursementBatchResource::collection($data['existingBatches'] ?? []),
            'adminUsers' => ReimbursementRecipientResource::collection($data['adminUsers'] ?? []),
            'itemsPayload' => ReimbursementUnderProcessingItemResource::collection($data['itemsPayload'] ?? []),
        ]);
    }

    public function markDonation(Request $request): JsonResponse
    {
        $data = array_merge($request->query->all(), $request->all(), $request->json()->all());
        $validator = Validator::make($data, [
            'expense_id' => ['required', 'integer', 'min:1'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        $payload = $validator->validated();
        $userId = (int) (auth()->id() ?? 0);

        try {
            $this->donationService->markDonation((int) $payload['expense_id'], $userId);
        } catch (RuntimeException $e) {
            $message = $e->getMessage();
            $status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422);
            return response()->json(['ok' => false, 'message' => $message], $status);
        } catch (\Throwable $e) {
            return response()->json(['ok' => false, 'message' => 'Unable to mark donation right now.'], 500);
        }

        return response()->json(['ok' => true]);
    }

    public function createBatch(Request $request): JsonResponse
    {
        $data = array_merge($request->query->all(), $request->all(), $request->json()->all());
        $validator = Validator::make($data, [
            'title' => ['nullable', 'string', 'max:255'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        $payload = $validator->validated();
        $userId = (int) (auth()->id() ?? 0);
        $schoolYear = $this->context->getSchoolYear();
        $semester = $this->context->getSemester();

        $batch = $this->batchService->createBatch($payload['title'] ?? null, $userId, $schoolYear, $semester);

        return response()->json([
            'ok' => true,
            'batch' => $batch,
        ], 201);
    }

    public function updateBatchAssignment(ReimbursementBatchAssignmentRequest $request): JsonResponse
    {
        $payload = $request->validated();

        $batchId = (int) ($payload['batch_id'] ?? 0);
        $batchNumber = $payload['batch_number'] ?? null;
        if ($batchId <= 0 && $batchNumber !== null && trim((string) $batchNumber) !== '') {
            $batchId = (int) $batchNumber;
        }

        $adminIdRaw = $payload['admin_id'] ?? null;
        $adminId = ($adminIdRaw === '' || $adminIdRaw === null) ? null : (int) $adminIdRaw;
        $reimbursementId = !empty($payload['reimbursement_id']) ? (int) $payload['reimbursement_id'] : null;

        try {
            $result = $this->assignmentService->updateAssignment(
                (int) $payload['expense_id'],
                $batchId,
                $adminId,
                $reimbursementId,
                $this->context->getSchoolYear(),
                $this->context->getSemester()
            );
        } catch (RuntimeException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
        }

        return response()->json([
            'ok' => true,
            'assignment' => $result,
        ]);
    }

    public function lockBatch(ReimbursementBatchLockRequest $request): JsonResponse
    {
        $payload = $request->validated();
        $userId = (int) (auth()->id() ?? 0);

        try {
            $this->batchService->lockBatch(
                (int) $payload['batch_id'],
                $userId,
                $this->context->getSchoolYear(),
                $this->context->getSemester()
            );
        } catch (RuntimeException $e) {
            $message = $e->getMessage();
            $status = str_contains($message, 'check file') ? 409 : 404;
            return response()->json(['ok' => false, 'message' => $message], $status);
        } catch (\Throwable $e) {
            return response()->json(['ok' => false, 'message' => 'Unable to lock batch right now.'], 500);
        }

        return response()->json(['ok' => true, 'status' => 'closed']);
    }

    public function uploadBatchAdminFile(ReimbursementBatchAdminFileRequest $request): JsonResponse
    {
        $payload = $request->validated();
        $batchId = (int) $payload['batch_id'];
        $adminId = (int) $payload['admin_id'];

        $batch = ReimbursementBatch::query()->find($batchId);
        if (!$batch) {
            return response()->json(['ok' => false, 'message' => 'Batch not found.'], 404);
        }
        if (strtolower((string) ($batch->status ?? '')) !== ReimbursementBatch::STATUS_OPEN) {
            return response()->json(['ok' => false, 'message' => 'Cannot upload files for a locked batch.'], 409);
        }

        try {
            $file = $this->fileService->storeAdminFile($batchId, $adminId, $request->file('check_file'), (int) (auth()->id() ?? 0));
        } catch (\Throwable $e) {
            return response()->json(['ok' => false, 'message' => 'Unable to store the uploaded file.'], 500);
        }

        return response()->json([
            'ok' => true,
            'file' => $file,
        ]);
    }

    public function serveAdminCheckFile(FileNameRequest $request, string $name, string $mode = 'inline'): Response|JsonResponse
    {
        $allowedExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'];
        $meta = $this->fileServeService->meta(storage_path('uploads/reimbursements'), $name, $allowedExtensions, null);
        $disposition = strtolower(trim($mode)) === 'download' ? 'attachment' : 'inline';

        return response(file_get_contents($meta['path']), 200, [
            'Content-Type' => $meta['mime'],
            'Content-Disposition' => $disposition . '; filename="' . $meta['download_name'] . '"',
            'Content-Length' => (string) $meta['size'],
            'ETag' => $meta['etag'],
            'Last-Modified' => $meta['last_modified'],
        ]);
    }

    public function index(ReimbursementIndexRequest $request): JsonResponse
    {
        $payload = $request->validated();
        $filters = [
            'school_year' => $payload['school_year'] ?? null,
            'semester' => $payload['semester'] ?? null,
            'status' => $payload['status'] ?? null,
            'user_id' => $payload['user_id'] ?? null,
        ];

        $data = $this->queryService->index($filters);

        return response()->json([
            'ok' => true,
            'expenses' => ReimbursementExpenseResource::collection($data['expenses'] ?? []),
            'users' => ReimbursementRecipientResource::collection($data['users'] ?? []),
            'schoolYears' => $data['schoolYears'] ?? [],
            'batchSummaries' => $data['batchSummaries'] ?? [],
            'batchDetails' => $data['batchDetails'] ?? [],
            'batchAttachments' => $data['batchAttachments'] ?? [],
            'donationBatch' => $data['donationBatch'] ?? null,
            'donationDetails' => $data['donationDetails'] ?? null,
        ]);
    }

    public function sendBatchEmail(ReimbursementBatchEmailRequest $request): JsonResponse
    {
        $payload = $request->validated();
        $receiptIds = $payload['receipts'] ?? [];
        $checkIds = $payload['checks'] ?? [];

        try {
            $ok = $this->emailService->sendBatchEmail(
                (int) $payload['batch_id'],
                $payload['recipient_email'],
                (string) ($payload['message'] ?? ''),
                $receiptIds,
                $checkIds
            );
        } catch (RuntimeException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], 404);
        }

        if (!$ok) {
            return response()->json(['ok' => false, 'message' => 'Failed to send email. Please try again.'], 500);
        }

        return response()->json(['ok' => true, 'message' => 'Email sent successfully.']);
    }

    public function export(ReimbursementExportRequest $request): Response
    {
        $payload = $request->validated();
        $type = $payload['type'] ?? 'processed';

        if ($type === 'under_processing') {
            $csv = $this->exportService->buildUnderProcessingCsv();
        } else {
            $csv = $this->exportService->buildProcessedCsv($payload);
        }

        return response()->streamDownload(function () use ($csv) {
            $out = fopen('php://output', 'w');
            fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
            foreach ($csv['rows'] as $row) {
                fputcsv($out, $row);
            }
            fclose($out);
        }, $csv['filename'], [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    public function exportBatch(ReimbursementExportBatchRequest $request): Response
    {
        $payload = $request->validated();
        $csv = $this->exportService->buildBatchCsv((int) $payload['batch_id']);

        return response()->streamDownload(function () use ($csv) {
            $out = fopen('php://output', 'w');
            fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
            foreach ($csv['rows'] as $row) {
                fputcsv($out, $row);
            }
            fclose($out);
        }, $csv['filename'], [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    public function store(Request $request): JsonResponse
    {
        $data = array_merge($request->query->all(), $request->all(), $request->json()->all());
        $validator = Validator::make($data, [
            'amount' => ['required', 'numeric', 'gt:0'],
            'reimbursed_to' => ['required', 'integer', 'min:1'],
            'reimbursement_method' => ['required', 'in:Cash,Check'],
            'check_number' => ['required_if:reimbursement_method,Check', 'nullable', 'string', 'max:50'],
            'receipt' => ['required_if:reimbursement_method,Check', 'nullable', 'file', 'max:2048', 'mimes:jpg,jpeg,png,webp,gif,pdf'],
            'expense_id' => ['nullable', 'integer', 'min:1'],
            'description' => ['nullable', 'string'],
            'school_year' => ['nullable', 'string', 'max:20'],
            'semester' => ['nullable', 'string', 'max:20'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        $payload = $validator->validated();
        $userId = (int) (auth()->id() ?? 0);
        $receiptName = null;

        if ($request->hasFile('receipt')) {
            $receiptName = $this->fileService->storeReceipt($request->file('receipt'));
        }

        try {
            $reimb = $this->crudService->store(
                $payload,
                $userId,
                $this->context->getSchoolYear(),
                $this->context->getSemester(),
                $receiptName
            );
        } catch (RuntimeException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
        }

        $data = $reimb->toArray();
        $data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);

        return response()->json([
            'ok' => true,
            'reimbursement' => new ReimbursementResource($data),
        ], 201);
    }

    public function process(ReimbursementProcessRequest $request): JsonResponse
    {
        $payload = $request->validated();
        $userId = (int) (auth()->id() ?? 0);
        $receiptName = null;

        if ($request->hasFile('receipt')) {
            $receiptName = $this->fileService->storeReceipt($request->file('receipt'));
        }

        try {
            $reimb = $this->crudService->process(
                $payload,
                $userId,
                $this->context->getSchoolYear(),
                $this->context->getSemester(),
                $receiptName
            );
        } catch (RuntimeException $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
        }

        $data = $reimb->toArray();
        $data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null);

        return response()->json([
            'ok' => true,
            'reimbursement' => new ReimbursementResource($data),
        ], 201);
    }

    public function reimbursedExpenses(): JsonResponse
    {
        $expenses = $this->queryService->reimbursedExpenses();

        return response()->json([
            'ok' => true,
            'expenses' => $expenses,
        ]);
    }

    public function update(Request $request, int $id): JsonResponse
    {
        $reimb = Reimbursement::query()->find($id);
        if (!$reimb) {
            return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404);
        }

        $data = array_merge($request->query->all(), $request->all(), $request->json()->all());
        $validator = Validator::make($data, [
            'amount' => ['required', 'numeric', 'gt:0'],
            'reimbursed_to' => ['required', 'integer', 'min:1'],
            'reimbursement_method' => ['required', 'in:Cash,Check'],
            'check_number' => ['required_if:reimbursement_method,Check', 'nullable', 'string', 'max:50'],
            'receipt' => ['nullable', 'file', 'max:2048', 'mimes:jpg,jpeg,png,webp,gif,pdf'],
            'remove_receipt' => ['nullable', 'boolean'],
            'description' => ['nullable', 'string'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 422);
        }

        $payload = $validator->validated();
        $userId = (int) (auth()->id() ?? 0);

        $receiptName = $reimb->receipt_path;
        if ($request->hasFile('receipt')) {
            $receiptName = $this->fileService->storeReceipt($request->file('receipt'));
        }
        if (!empty($payload['remove_receipt'])) {
            $receiptName = null;
        }

        $updated = $this->crudService->update($reimb, $payload, $userId, $receiptName);
        $data = $updated->toArray();
        $data['receipt_url'] = $this->fileService->receiptUrl($updated->receipt_path ?? null);

        return response()->json([
            'ok' => true,
            'reimbursement' => new ReimbursementResource($data),
        ]);
    }
}
