<?php

declare(strict_types=1);

namespace App\Http\Controllers\Report;

use App\Http\Controllers\Concerns\AuthorizesProjectAccess;
use App\Http\Controllers\Controller;
use App\Http\Requests\Report\GrowthWorkflowRequest;
use App\Http\Requests\Report\HealthReportRequest;
use App\Http\Requests\Report\IndexHealthReportRequest;
use App\Http\Resources\ReportResource;
use App\Models\Project;
use App\Models\Report;
use App\Models\ReportConsumable;
use App\Models\ReportInvoiceItem;
use App\Models\ReportItem;
use App\Notifications\SimpleDatabaseNotification;
use App\Services\HealthTreatmentSyncService;
use App\Support\ApiResponse;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;

class HealthReportController extends Controller
{
    use AuthorizesProjectAccess;

    public function __construct(
        private readonly HealthTreatmentSyncService $treatmentSync,
    ) {}

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

        $query = Report::query()
            ->where('type', Report::TYPE_HEALTH)
            ->with([
                'project',
                'producer.user',
                'user',
                'product',
                'items.product',
                'consumables',
                'invoiceItems',
            ]);

        $this->restrictVisibility($query, $request->user());
        $this->applyFilters($query, $request);

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

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

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

        if ($request->filled('project_id')) {
            $project = Project::findOrFail($request->integer('project_id'));
            $this->authorizeProjectAccess($project);
            $this->assertCanManageHealth($request->user(), $project);
        }

        try {
            DB::beginTransaction();

            $isDraft = $request->boolean('is_draft') || $request->input('status') === 'draft';
            $data = $this->payload($request, $isDraft);
            $items = $data['items'] ?? [];
            $consumables = $data['consumables'] ?? null;
            $invoiceItems = $data['invoice_items'] ?? null;
            unset($data['items'], $data['consumables'], $data['invoice_items'], $data['is_draft']);

            $report = Report::create($data);
            $this->syncItems($report, $items);
            $this->syncConsumables($report, $consumables);
            $this->syncInvoiceItems($report, $invoiceItems);

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

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

        if (! $isDraft) {
            $this->notifyAdmins($report, 'گزارش سلامت جدید برای بررسی ارسال شد.');
        }

        return ApiResponse::created(
            new ReportResource($report->load([
                'items.product', 'consumables', 'invoiceItems', 'project', 'producer', 'user',
            ])),
            $isDraft ? 'پیش‌نویس گزارش سلامت ذخیره شد.' : 'گزارش سلامت ثبت و برای تأیید ارسال شد.'
        );
    }

    public function show(Report $report)
    {
        $this->ensureHealth($report);
        Gate::authorize('view', $report);

        return new ReportResource($report->load([
            'items.product',
            'consumables',
            'invoiceItems',
            'project',
            'producer.user',
            'user',
            'product',
        ]));
    }

    public function update(HealthReportRequest $request, Report $report)
    {
        $this->ensureHealth($report);
        Gate::authorize('update', $report);
        $this->assertCanManageHealth($request->user(), $report->project);

        if (in_array($report->workflow_status, [Report::WORKFLOW_APPROVED], true) && ! $request->user()->hasRole('admin')) {
            return ApiResponse::forbidden('گزارش تأییدشده قابل ویرایش نیست.');
        }

        try {
            DB::beginTransaction();

            $isDraft = $request->boolean('is_draft') || $request->input('status') === 'draft';
            $data = $this->payload($request, $isDraft, $report);
            $items = $data['items'] ?? null;
            $consumables = $data['consumables'] ?? null;
            $invoiceItems = $data['invoice_items'] ?? null;
            unset($data['items'], $data['consumables'], $data['invoice_items'], $data['is_draft']);

            if ($report->workflow_status === Report::WORKFLOW_NEEDS_EDIT && ! $isDraft) {
                $data['workflow_status'] = Report::WORKFLOW_PENDING;
                $data['admin_reason'] = null;
            }

            $report->update($data);

            if ($items !== null) {
                $this->syncItems($report, $items);
            }
            if ($consumables !== null) {
                $this->syncConsumables($report, $consumables);
            }
            if ($invoiceItems !== null) {
                $this->syncInvoiceItems($report, $invoiceItems);
            }

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

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

        return ApiResponse::success(
            new ReportResource($report->fresh()->load([
                'items.product', 'consumables', 'invoiceItems', 'project', 'producer', 'user',
            ])),
            'گزارش سلامت به‌روزرسانی شد.'
        );
    }

    public function submit(Report $report)
    {
        $this->ensureHealth($report);
        Gate::authorize('update', $report);
        $this->assertCanManageHealth(Auth::user(), $report->project);

        if ($report->status === 'published' && $report->workflow_status === Report::WORKFLOW_PENDING) {
            return ApiResponse::badRequest('گزارش قبلاً برای تأیید ارسال شده است.');
        }

        $report->update([
            'status' => 'published',
            'workflow_status' => Report::WORKFLOW_PENDING,
            'admin_reason' => null,
            'updated_by' => Auth::id(),
        ]);

        $this->notifyAdmins($report, 'گزارش سلامت برای بررسی ارسال شد.');

        return ApiResponse::success(
            new ReportResource($report->fresh()->load([
                'items.product', 'consumables', 'invoiceItems', 'project', 'producer',
            ])),
            'گزارش برای تأیید ارسال شد.'
        );
    }

    public function approve(GrowthWorkflowRequest $request, Report $report)
    {
        $this->ensureHealth($report);
        $this->authorizeAdmin();

        if (($report->workflow_status ?? Report::WORKFLOW_PENDING) !== Report::WORKFLOW_PENDING) {
            return ApiResponse::badRequest('فقط گزارش در انتظار تأیید قابل تأیید است.');
        }

        try {
            DB::beginTransaction();

            $report->update([
                'workflow_status' => Report::WORKFLOW_APPROVED,
                'status' => 'published',
                'admin_reason' => null,
                'updated_by' => Auth::id(),
            ]);

            $this->treatmentSync->syncFromApprovedReport(
                $report->fresh(['items.product.breed', 'producer'])
            );

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

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

        $this->notifySender($report, 'تأیید گزارش سلامت', "گزارش سلامت «{$report->title}» تأیید شد.");

        return ApiResponse::success(
            new ReportResource($report->fresh()->load([
                'items.product', 'consumables', 'invoiceItems', 'project', 'producer',
            ])),
            'گزارش سلامت تأیید شد.'
        );
    }

    public function reject(GrowthWorkflowRequest $request, Report $report)
    {
        $this->ensureHealth($report);
        $this->authorizeAdmin();

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

        $report->update([
            'workflow_status' => Report::WORKFLOW_REJECTED,
            'admin_reason' => $reason,
            'updated_by' => Auth::id(),
        ]);

        $this->notifySender($report, 'رد گزارش سلامت', "گزارش سلامت «{$report->title}» رد شد. دلیل: {$reason}");

        return ApiResponse::success(
            new ReportResource($report->fresh()->load([
                'items.product', 'consumables', 'invoiceItems', 'project', 'producer',
            ])),
            'گزارش سلامت رد شد.'
        );
    }

    public function needsEdit(GrowthWorkflowRequest $request, Report $report)
    {
        $this->ensureHealth($report);
        $this->authorizeAdmin();

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

        $report->update([
            'workflow_status' => Report::WORKFLOW_NEEDS_EDIT,
            'admin_reason' => $reason,
            'updated_by' => Auth::id(),
        ]);

        $this->notifySender($report, 'نیاز به ویرایش گزارش سلامت', "گزارش سلامت «{$report->title}» نیاز به ویرایش دارد. دلیل: {$reason}");

        return ApiResponse::success(
            new ReportResource($report->fresh()->load([
                'items.product', 'consumables', 'invoiceItems', 'project', 'producer',
            ])),
            'گزارش برای ویرایش به فرستنده ارجاع شد.'
        );
    }

    public function destroy(Report $report)
    {
        $this->ensureHealth($report);
        Gate::authorize('delete', $report);

        if (
            ! Auth::user()?->hasRole('admin')
            && $report->status !== 'draft'
            && $report->workflow_status !== Report::WORKFLOW_NEEDS_EDIT
        ) {
            return ApiResponse::badRequest('فقط پیش‌نویس یا گزارش نیازمند ویرایش قابل حذف است.');
        }

        $report->delete();

        return ApiResponse::success(message: 'گزارش سلامت حذف شد.');
    }

    public function history(IndexHealthReportRequest $request, Project $project)
    {
        $this->authorizeProjectAccess($project);
        $project->loadMissing('producer');

        $query = Report::query()
            ->where('project_id', $project->id)
            ->where('type', Report::TYPE_HEALTH)
            ->with([
                'items.product',
                'producer.user',
                'user',
                'consumables',
                'invoiceItems',
                'product',
            ]);

        if (! $request->user()->hasRole('admin')) {
            $user = $request->user();
            $isProducer = $project->producer && $project->producer->user_id === $user->id;
            if (! $isProducer && $user->id !== $project->user_id && $user->id !== $project->created_by) {
                $query->where('workflow_status', Report::WORKFLOW_APPROVED);
            } elseif (! $isProducer) {
                $query->where(function (Builder $q) use ($user): void {
                    $q->where('workflow_status', Report::WORKFLOW_APPROVED)
                        ->orWhere('created_by', $user->id)
                        ->orWhere('user_id', $user->id);
                });
            }
        }

        $this->applyFilters($query, $request);

        $reports = $query->get()->sortByDesc(fn (Report $r) => $r->report_at ?? $r->date)->values();

        return ApiResponse::success([
            'project' => [
                'id' => $project->id,
                'code' => $project->code,
                'name' => $project->name,
            ],
            'summary' => [
                'total_reports' => $reports->count(),
                'approved' => $reports->where('workflow_status', Report::WORKFLOW_APPROVED)->count(),
                'pending' => $reports->where('workflow_status', Report::WORKFLOW_PENDING)->count(),
                'treatments' => $reports->where('report_type', Report::HEALTH_TYPE_TREATMENT)->count(),
                'removals' => $reports->where('report_type', Report::HEALTH_TYPE_REMOVE_PRODUCT)->count(),
                'vaccinations' => $reports->where('report_type', Report::HEALTH_TYPE_VACCINATION)->count(),
            ],
            'reports' => ReportResource::collection($reports),
        ]);
    }

    public function latest(Request $request, Project $project)
    {
        $this->authorizeProjectAccess($project);

        $report = Report::query()
            ->where('project_id', $project->id)
            ->where('type', Report::TYPE_HEALTH)
            ->where('workflow_status', Report::WORKFLOW_APPROVED)
            ->with([
                'producer.user',
                'product',
                'items.product',
                'consumables',
                'invoiceItems',
            ])
            ->orderByDesc('report_at')
            ->orderByDesc('date')
            ->orderByDesc('id')
            ->first();

        if (! $report) {
            return ApiResponse::success(null, 'گزارش سلامتی یافت نشد.');
        }

        return ApiResponse::success([
            'report_date' => optional($report->report_at ?? $report->date)?->toDateString(),
            'service_provider' => $report->producer ? [
                'id' => $report->producer->id,
                'name' => $report->producer->brand_name ?? $report->producer->user?->name,
                'mobile' => $report->producer->user?->mobile,
            ] : null,
            'textual_report' => $report->desc ?? $report->written_report,
            'expert_opinion' => $report->expert_opinion,
            'diagnosis' => $report->diagnosis,
            'disease_name' => $report->disease_name,
            'report_type' => $report->report_type,
            'report_type_label' => Report::healthReportTypes()[$report->report_type] ?? null,
            'images' => $this->mapFiles($report->attachments),
            'documents' => $this->mapFiles($report->documents),
            'written_report' => $report->written_report,
            'report' => new ReportResource($report),
        ]);
    }

    public function download(Request $request, Report $report)
    {
        $this->ensureHealth($report);
        Gate::authorize('view', $report);

        $report->load([
            'project', 'producer.user', 'product', 'items.product', 'consumables', 'invoiceItems',
        ]);

        return ApiResponse::success([
            'report' => new ReportResource($report),
            'texts' => [
                'title' => $report->title,
                'desc' => $report->desc,
                'written_report' => $report->written_report,
                'diagnosis' => $report->diagnosis,
                'expert_opinion' => $report->expert_opinion,
                'disease_name' => $report->disease_name,
                'admin_reason' => $report->admin_reason,
            ],
            'files' => [
                'attachments' => $this->mapFiles($report->attachments),
                'documents' => $this->mapFiles($report->documents),
                'invoice_image' => $report->invoice_image
                    ? ['path' => $report->invoice_image, 'url' => url($report->invoice_image)]
                    : null,
            ],
            'invoice' => [
                'total_cost' => $report->total_cost,
                'items' => $report->invoiceItems,
                'consumables' => $report->consumables,
            ],
        ]);
    }

    private function payload(HealthReportRequest $request, bool $isDraft, ?Report $existing = null): array
    {
        $data = $request->safe()->except(['is_draft']);
        $data['type'] = Report::TYPE_HEALTH;
        $data['created_by'] = $existing?->created_by ?? Auth::id();
        $data['updated_by'] = Auth::id();
        $data['user_id'] = $data['user_id'] ?? $existing?->user_id ?? Auth::id();
        $data['report_at'] = $data['report_at'] ?? $data['date'] ?? null;
        $data['date'] = isset($data['date'])
            ? date('Y-m-d', strtotime((string) $data['date']))
            : (isset($data['report_at']) ? date('Y-m-d', strtotime((string) $data['report_at'])) : null);
        $data['title'] = $data['title']
            ?? ('گزارش سلامت ' . ($data['date'] ?? now()->toDateString()));
        $data['status'] = $isDraft ? 'draft' : 'published';
        $data['workflow_status'] = $isDraft
            ? ($existing?->workflow_status ?? Report::WORKFLOW_PENDING)
            : Report::WORKFLOW_PENDING;

        if (! $request->filled('producer_id') && $request->filled('project_id')) {
            $data['producer_id'] = Project::find($request->integer('project_id'))?->producer_id;
        }

        if (empty($data['product_id']) && ! empty($data['items'][0]['product_id'])) {
            $data['product_id'] = $data['items'][0]['product_id'];
        }

        return $data;
    }

    private function syncItems(Report $report, array $items): void
    {
        $report->items()->delete();

        foreach ($items as $index => $item) {
            ReportItem::create([
                'report_id' => $report->id,
                'product_id' => $item['product_id'] ?? null,
                'value' => $item['value'] ?? null,
                'unit' => $item['unit'] ?? null,
                'sort_order' => $index,
            ]);
        }
    }

    private function syncConsumables(Report $report, ?array $consumables): void
    {
        if ($consumables === null) {
            return;
        }

        $report->consumables()->delete();
        foreach ($consumables as $index => $row) {
            ReportConsumable::create([
                'report_id' => $report->id,
                'name' => $row['name'],
                'unit' => $row['unit'] ?? null,
                'quantity' => $row['quantity'] ?? 0,
                'unit_price' => $row['unit_price'] ?? null,
                'sort_order' => $index,
            ]);
        }
    }

    private function syncInvoiceItems(Report $report, ?array $invoiceItems): void
    {
        if ($invoiceItems === null) {
            return;
        }

        $report->invoiceItems()->delete();
        foreach ($invoiceItems as $index => $invoiceItem) {
            ReportInvoiceItem::create([
                'report_id' => $report->id,
                'title' => $invoiceItem['title'],
                'amount' => $invoiceItem['amount'] ?? 0,
                'sort_order' => $index,
            ]);
        }
    }

    private function mapFiles(?array $paths): array
    {
        return collect($paths ?? [])
            ->filter()
            ->map(fn ($path) => [
                'path' => $path,
                'url' => str_starts_with((string) $path, 'http') ? $path : url($path),
            ])
            ->values()
            ->all();
    }

    private function restrictVisibility(Builder $query, $user): void
    {
        if ($user->hasRole('admin')) {
            return;
        }

        $query->where(function (Builder $scope) use ($user): void {
            $scope->where('user_id', $user->id)
                ->orWhere('created_by', $user->id)
                ->orWhereHas('project', function (Builder $projectQuery) use ($user): void {
                    $projectQuery->where('user_id', $user->id)
                        ->orWhere('created_by', $user->id)
                        ->orWhereHas('producer', fn (Builder $p) => $p->where('user_id', $user->id));
                });
        });
    }

    private function applyFilters(Builder $query, IndexHealthReportRequest $request): void
    {
        if ($request->filled('project_id')) {
            $query->where('project_id', $request->integer('project_id'));
        }

        if ($request->filled('project')) {
            $term = $request->string('project');
            $query->whereHas('project', function (Builder $q) use ($term): void {
                $q->where('name', 'like', "%{$term}%")
                    ->orWhere('code', 'like', "%{$term}%");
            });
        }

        if ($request->filled('product_id')) {
            $productId = $request->integer('product_id');
            $query->where(function (Builder $q) use ($productId): void {
                $q->where('product_id', $productId)
                    ->orWhereHas('items', fn (Builder $i) => $i->where('product_id', $productId));
            });
        }

        if ($request->filled('product')) {
            $term = $request->string('product');
            $query->where(function (Builder $q) use ($term): void {
                $q->whereHas('product', fn (Builder $pq) => $pq->where('name', 'like', "%{$term}%")->orWhere('code', 'like', "%{$term}%"))
                    ->orWhereHas('items.product', fn (Builder $pq) => $pq->where('name', 'like', "%{$term}%")->orWhere('code', 'like', "%{$term}%"));
            });
        }

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

        if ($request->filled('producer')) {
            $term = $request->string('producer');
            $query->whereHas('producer', function (Builder $q) use ($term): void {
                $q->where('brand_name', 'like', "%{$term}%")
                    ->orWhereHas('user', fn (Builder $uq) => $uq->where('name', 'like', "%{$term}%"));
            });
        }

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

        if ($request->filled('disease_name')) {
            $query->where('disease_name', 'like', '%'.$request->string('disease_name').'%');
        }

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

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

        if ($request->filled('from')) {
            $from = $request->date('from');
            $query->where(function (Builder $q) use ($from): void {
                $q->whereDate('report_at', '>=', $from)
                    ->orWhere(function (Builder $inner) use ($from): void {
                        $inner->whereNull('report_at')->whereDate('date', '>=', $from);
                    });
            });
        }

        if ($request->filled('to')) {
            $to = $request->date('to');
            $query->where(function (Builder $q) use ($to): void {
                $q->whereDate('report_at', '<=', $to)
                    ->orWhere(function (Builder $inner) use ($to): void {
                        $inner->whereNull('report_at')->whereDate('date', '<=', $to);
                    });
            });
        }
    }

    private function ensureHealth(Report $report): void
    {
        if (! $report->isHealthReport()) {
            abort(404, 'گزارش سلامت یافت نشد.');
        }
    }

    private function assertCanManageHealth($user, ?Project $project): void
    {
        if (! $user || ! $project) {
            return;
        }

        if ($user->hasRole('admin')) {
            return;
        }

        if ($project->producer && $project->producer->user_id === $user->id) {
            return;
        }

        abort(403, 'فقط ادمین یا تولیدکننده پروژه می‌تواند گزارش سلامت ثبت کند.');
    }

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

    private function notifySender(Report $report, string $title, string $body): void
    {
        $user = $report->user ?? $report->createdBy;
        if ($user) {
            $user->notify(new SimpleDatabaseNotification($title, $body, [
                'type' => 'health_workflow',
                'report_id' => $report->id,
                'workflow_status' => $report->workflow_status,
            ]));
        }
    }

    private function notifyAdmins(Report $report, string $message): void
    {
        \App\Models\User::role('admin')->get()->each(function ($admin) use ($report, $message): void {
            $admin->notify(new SimpleDatabaseNotification('گزارش سلامت', $message, [
                'type' => 'health_report_submitted',
                'report_id' => $report->id,
                'project_id' => $report->project_id,
            ]));
        });
    }
}
