<?php

declare(strict_types=1);

namespace App\Http\Controllers\Invoice;

use App\Http\Controllers\Concerns\AuthorizesProjectAccess;
use App\Http\Controllers\Controller;
use App\Http\Requests\Invoice\InvoiceListRequest;
use App\Http\Requests\Invoice\InvoiceWorkflowRequest;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Resources\InvoiceDetailResource;
use App\Http\Resources\InvoiceResource;
use App\Models\Invoice;
use App\Models\Project;
use App\Models\User;
use App\Services\InvoiceExportService;
use App\Services\InvoiceService;
use App\Support\ApiResponse;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;

class InvoiceController extends Controller
{
    use AuthorizesProjectAccess;

    public function __construct(
        private readonly InvoiceService $invoices,
        private readonly InvoiceExportService $exports,
    ) {}

    public function index(InvoiceListRequest $request)
    {
        Gate::authorize('viewAny', Invoice::class);

        $user = $request->user();
        $query = Invoice::query()
            ->with(['project', 'user', 'items'])
            ->when(! $user->hasRole('admin'), function ($builder) use ($user): void {
                $builder->where(function ($scope) use ($user): void {
                    $scope->where('user_id', $user->id)
                        ->orWhere('created_by', $user->id)
                        ->orWhereHas('project', function ($projectQuery) use ($user): void {
                            $projectQuery->where('user_id', $user->id)
                                ->orWhere('created_by', $user->id)
                                ->orWhereHas('producer', fn ($producerQuery) => $producerQuery->where('user_id', $user->id));
                        });
                });
            });

        if ($request->filled('project_id')) {
            $query->where('project_id', $request->integer('project_id'));
        }

        if ($request->filled('status')) {
            $query->where('status', $request->string('status'));
        }

        if ($request->filled('type')) {
            $query->where('type', $request->string('type'));
        }

        if ($request->filled('search')) {
            $term = $request->string('search');
            $query->where(function ($q) use ($term): void {
                $q->where('title', 'like', "%{$term}%")
                    ->orWhere('number', 'like', "%{$term}%")
                    ->orWhere('tracking_code', 'like', "%{$term}%");
            });
        }

        if ($request->filled('from')) {
            $query->whereDate('issued_at', '>=', $request->date('from'));
        }

        if ($request->filled('to')) {
            $query->whereDate('issued_at', '<=', $request->date('to'));
        }

        $perPage = min($request->integer('per_page', 15), 50);

        return InvoiceResource::collection(
            $request->boolean('noPaginate') ? $query->latest()->get() : $query->latest()->paginate($perPage)
        );
    }

    public function store(StoreInvoiceRequest $request)
    {
        Gate::authorize('create', Invoice::class);

        $project = Project::findOrFail($request->integer('project_id'));
        $this->authorizeProjectAccess($project);

        $isDraft = $request->boolean('is_draft') || $request->input('status') === Invoice::STATUS_DRAFT;

        try {
            DB::beginTransaction();

            $recipientIds = collect($request->input('recipient_ids', []))
                ->filter()
                ->unique()
                ->values();

            if ($recipientIds->isEmpty() && $request->filled('user_id')) {
                $recipientIds = collect([$request->integer('user_id')]);
            }

            if ($recipientIds->isEmpty()) {
                $recipientIds = collect([Auth::id()]);
            }

            $created = [];
            foreach ($recipientIds as $recipientId) {
                $invoice = Invoice::create($this->payload($request, $isDraft, (int) $recipientId));
                $this->invoices->syncItems($invoice, $request->input('items', []));
                $created[] = $invoice->fresh(['items', 'project', 'user']);
            }

            DB::commit();
        } catch (Exception $exception) {
            DB::rollBack();

            return ApiResponse::response(message: $exception->getMessage(), status: 419);
        }

        $message = $isDraft ? 'پیش‌نویس فاکتور ذخیره شد.' : 'فاکتور ثبت و برای تأیید ارسال شد.';

        if (count($created) === 1) {
            return ApiResponse::created(new InvoiceDetailResource($created[0]), $message);
        }

        return ApiResponse::created(
            InvoiceDetailResource::collection(collect($created)),
            $message
        );
    }

    public function show(Invoice $invoice)
    {
        Gate::authorize('view', $invoice);

        $invoice = $this->invoices->refreshLateFee($invoice);
        $invoice->load(['project', 'user', 'items', 'createdBy']);

        return new InvoiceDetailResource($invoice);
    }

    public function download(Request $request, Invoice $invoice)
    {
        Gate::authorize('view', $invoice);

        $data = $request->validate([
            'format' => ['nullable', 'string', Rule::in(['pdf', 'xlsx'])],
        ]);

        $export = $this->exports->export($invoice, $data['format'] ?? 'pdf');

        return ApiResponse::success([
            'url' => $export['url'],
            'format' => $export['format'],
        ], 'لینک دانلود آماده شد.');
    }

    public function update(StoreInvoiceRequest $request, Invoice $invoice)
    {
        Gate::authorize('update', $invoice);

        if (! $invoice->isEditable() && ! $request->user()->hasRole('admin')) {
            return ApiResponse::forbidden('این فاکتور قابل ویرایش نیست.');
        }

        $isDraft = $request->boolean('is_draft') || $request->input('status') === Invoice::STATUS_DRAFT;

        try {
            DB::beginTransaction();

            $data = $this->payload($request, $isDraft, (int) ($request->input('user_id') ?: $invoice->user_id), $invoice);

            if ($invoice->status === Invoice::STATUS_NEEDS_EDIT && ! $isDraft) {
                $data['status'] = Invoice::STATUS_PENDING;
                $data['admin_reason'] = null;
            }

            $invoice->update($data);
            $this->invoices->syncItems($invoice, $request->input('items', []));

            DB::commit();
        } catch (Exception $exception) {
            DB::rollBack();

            return ApiResponse::response(message: $exception->getMessage(), status: 419);
        }

        return ApiResponse::success(
            new InvoiceDetailResource($invoice->fresh(['items', 'project', 'user'])),
            'فاکتور به‌روزرسانی شد.'
        );
    }

    public function submit(Invoice $invoice)
    {
        Gate::authorize('update', $invoice);

        if (! in_array($invoice->status, [Invoice::STATUS_DRAFT, Invoice::STATUS_NEEDS_EDIT], true)) {
            return ApiResponse::badRequest('فقط پیش‌نویس یا فاکتور نیازمند ویرایش قابل ارسال است.');
        }

        $invoice->update([
            'status' => Invoice::STATUS_PENDING,
            'admin_reason' => null,
            'number' => $invoice->number ?: $this->invoices->generateNumber(),
            'issued_at' => $invoice->issued_at ?? now()->toDateString(),
        ]);

        return ApiResponse::success(
            new InvoiceDetailResource($invoice->fresh(['items', 'project', 'user'])),
            'فاکتور برای تأیید ادمین ارسال شد.'
        );
    }

    public function approve(InvoiceWorkflowRequest $request, Invoice $invoice)
    {
        $this->authorizeAdmin();
        Gate::authorize('approve', $invoice);

        if ($invoice->status !== Invoice::STATUS_PENDING && ! $request->user()->hasRole('admin')) {
            return ApiResponse::badRequest('فقط فاکتور در انتظار قابل تأیید است.');
        }

        if (! in_array($invoice->status, [Invoice::STATUS_PENDING, Invoice::STATUS_DRAFT], true)) {
            return ApiResponse::badRequest('وضعیت فاکتور برای تأیید معتبر نیست.');
        }

        $invoice = $this->invoices->approve($invoice, $request->user());

        return ApiResponse::success(
            new InvoiceDetailResource($invoice),
            'فاکتور تأیید شد.'
        );
    }

    public function reject(InvoiceWorkflowRequest $request, Invoice $invoice)
    {
        $this->authorizeAdmin();
        Gate::authorize('approve', $invoice);

        $reason = $request->input('admin_reason') ?? $request->input('reason');
        if (blank($reason)) {
            return ApiResponse::badRequest('ذکر دلیل رد الزامی است.');
        }

        $invoice = $this->invoices->reject($invoice, (string) $reason);

        return ApiResponse::success(
            new InvoiceDetailResource($invoice),
            'فاکتور رد شد.'
        );
    }

    public function needsEdit(InvoiceWorkflowRequest $request, Invoice $invoice)
    {
        $this->authorizeAdmin();
        Gate::authorize('approve', $invoice);

        $reason = $request->input('admin_reason') ?? $request->input('reason');
        if (blank($reason)) {
            return ApiResponse::badRequest('ذکر دلیل نیاز به ویرایش الزامی است.');
        }

        $invoice = $this->invoices->needsEdit($invoice, (string) $reason);

        return ApiResponse::success(
            new InvoiceDetailResource($invoice),
            'فاکتور برای ویرایش ارجاع شد.'
        );
    }

    public function destroy(Invoice $invoice)
    {
        Gate::authorize('delete', $invoice);

        if (! $invoice->isEditable() && ! Auth::user()?->hasRole('admin')) {
            return ApiResponse::forbidden('این فاکتور قابل حذف نیست.');
        }

        $invoice->delete();

        return ApiResponse::success(message: 'فاکتور حذف شد.');
    }

    public function recipients(Project $project)
    {
        $this->authorizeProjectAccess($project);

        $employers = $this->invoices->projectEmployers($project);
        $producerUser = $project->producer?->user;

        $users = $employers
            ->when($producerUser, fn ($c) => $c->push($producerUser))
            ->unique('id')
            ->values()
            ->map(fn (User $user) => [
                'id' => $user->id,
                'name' => trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) ?: $user->cellphone,
                'cellphone' => $user->cellphone,
                'email' => $user->email,
                'national_code' => $user->national_code,
                'role' => $producerUser && $producerUser->id === $user->id ? 'producer' : 'employer',
            ]);

        return ApiResponse::success(['recipients' => $users]);
    }

    /**
     * @return array<string, mixed>
     */
    private function payload(StoreInvoiceRequest $request, bool $isDraft, int $userId, ?Invoice $existing = null): array
    {
        $meta = $request->input('meta', $existing?->meta ?? []);

        return [
            'number' => $existing?->number ?: ($isDraft ? null : $this->invoices->generateNumber()),
            'project_id' => $request->integer('project_id'),
            'user_id' => $userId,
            'type' => $request->string('type'),
            'title' => $request->string('title'),
            'status' => $isDraft ? Invoice::STATUS_DRAFT : Invoice::STATUS_PENDING,
            'description' => $request->input('description'),
            'issued_at' => $request->input('issued_at') ?: ($existing?->issued_at?->toDateString() ?? now()->toDateString()),
            'due_at' => $request->input('due_at'),
            'commission_percent' => $request->input('commission_percent', config('finance.commission_percent')),
            'tracking_code' => $request->input('tracking_code'),
            'payment_method' => $request->input('payment_method'),
            'counterparty_name' => $request->input('counterparty_name'),
            'counterparty_phone' => $request->input('counterparty_phone'),
            'meta' => $meta,
            'created_by' => $existing?->created_by ?? Auth::id(),
        ];
    }

    private function authorizeAdmin(): void
    {
        if (! Auth::user()?->hasRole('admin')) {
            abort(403, 'فقط ادمین مجاز است.');
        }
    }
}
