<?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\IndexRationReportRequest;
use App\Http\Requests\Report\RationReportRequest;
use App\Http\Requests\Report\StoreFeedingLogRequest;
use App\Http\Resources\FeedingLogResource;
use App\Http\Resources\ReportResource;
use App\Models\Project;
use App\Models\Report;
use App\Models\ReportFeedingLog;
use App\Models\ReportFeedingMeal;
use App\Models\ReportItem;
use App\Models\ReportRationIngredient;
use App\Models\ReportRationWeekly;
use App\Notifications\SimpleDatabaseNotification;
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 RationReportController extends Controller
{
    use AuthorizesProjectAccess;

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

        $query = Report::query()
            ->where('type', Report::TYPE_RATION)
            ->with([
                'project',
                'producer.user',
                'user',
                'items.product',
                'rationIngredients',
                'feedingMeals',
                'rationWeekly',
            ]);

        $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(RationReportRequest $request)
    {
        Gate::authorize('create', Report::class);

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

        try {
            DB::beginTransaction();

            $isDraft = $request->boolean('is_draft') || $request->input('status') === 'draft';
            $data = $this->payload($request, $isDraft);
            $items = $data['items'] ?? [];
            $ingredients = $data['ration_ingredients'] ?? null;
            $meals = $data['feeding_meals'] ?? null;
            $weekly = $data['ration_weekly'] ?? null;
            unset(
                $data['items'],
                $data['ration_ingredients'],
                $data['feeding_meals'],
                $data['ration_weekly'],
                $data['is_draft']
            );

            $report = Report::create($data);
            $this->syncItems($report, $items);
            $this->syncIngredients($report, $ingredients);
            $this->syncMeals($report, $meals);
            $this->syncWeekly($report, $weekly);

            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($this->detailRelations())),
            $isDraft ? 'پیش‌نویس جیره ذخیره شد.' : 'جیره ثبت و برای تأیید ارسال شد.'
        );
    }

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

        return new ReportResource($report->load($this->detailRelations()));
    }

    public function update(RationReportRequest $request, Report $report)
    {
        $this->ensureRation($report);
        Gate::authorize('update', $report);
        $this->assertCanManageRation($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;
            $ingredients = $data['ration_ingredients'] ?? null;
            $meals = $data['feeding_meals'] ?? null;
            $weekly = $data['ration_weekly'] ?? null;
            unset(
                $data['items'],
                $data['ration_ingredients'],
                $data['feeding_meals'],
                $data['ration_weekly'],
                $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 ($ingredients !== null) {
                $this->syncIngredients($report, $ingredients);
            }
            if ($meals !== null) {
                $this->syncMeals($report, $meals);
            }
            if ($weekly !== null) {
                $this->syncWeekly($report, $weekly);
            }

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

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

        return ApiResponse::success(
            new ReportResource($report->fresh()->load($this->detailRelations())),
            'جیره به‌روزرسانی شد.'
        );
    }

    public function submit(Report $report)
    {
        $this->ensureRation($report);
        Gate::authorize('update', $report);
        $this->assertCanManageRation(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($this->detailRelations())),
            'جیره برای تأیید ارسال شد.'
        );
    }

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

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

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

        $this->notifySender($report, 'تأیید جیره', "جیره «{$report->title}» تأیید شد.");

        return ApiResponse::success(
            new ReportResource($report->fresh()->load($this->detailRelations())),
            'جیره تأیید شد.'
        );
    }

    public function reject(GrowthWorkflowRequest $request, Report $report)
    {
        $this->ensureRation($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($this->detailRelations())),
            'جیره رد شد.'
        );
    }

    public function needsEdit(GrowthWorkflowRequest $request, Report $report)
    {
        $this->ensureRation($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($this->detailRelations())),
            'جیره برای ویرایش به فرستنده ارجاع شد.'
        );
    }

    public function destroy(Report $report)
    {
        $this->ensureRation($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(IndexRationReportRequest $request, Project $project)
    {
        $this->authorizeProjectAccess($project);
        $project->loadMissing('producer');

        $query = Report::query()
            ->where('project_id', $project->id)
            ->where('type', Report::TYPE_RATION)
            ->with($this->detailRelations());

        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();
        $approved = $reports->where('workflow_status', Report::WORKFLOW_APPROVED)->values();

        return ApiResponse::success([
            'project' => [
                'id' => $project->id,
                'code' => $project->code,
                'name' => $project->name,
            ],
            'summary' => [
                'total_reports' => $reports->count(),
                'approved' => $approved->count(),
                'pending' => $reports->where('workflow_status', Report::WORKFLOW_PENDING)->count(),
                'rejected' => $reports->where('workflow_status', Report::WORKFLOW_REJECTED)->count(),
                'needs_edit' => $reports->where('workflow_status', Report::WORKFLOW_NEEDS_EDIT)->count(),
                'latest_total_cost' => $approved->first()?->rationTotalCost(),
                'latest_cost_per_head' => $approved->first()?->rationCostPerHead(),
            ],
            'reports' => ReportResource::collection($reports),
        ]);
    }

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

        $report = $this->latestApproved($project);

        if (! $report) {
            return ApiResponse::success(null, 'جیره فعالی یافت نشد.');
        }

        $ingredients = $report->rationIngredients;
        $totalQty = $report->rationTotalQuantity();

        return ApiResponse::success([
            'service_provider' => $report->producer ? [
                'id' => $report->producer->id,
                'name' => $report->producer->brand_name ?? $report->producer->user?->name,
                'specialty' => $report->producer->getAttribute('specialty'),
                'experience' => $report->producer->getAttribute('experience'),
                'mobile' => $report->producer->user?->mobile,
            ] : null,
            'ration_type' => $report->ration_type,
            'last_updated_at' => optional($report->report_at ?? $report->date)?->toDateString(),
            'summary' => $report->rationSummary(),
            'ingredients' => $ingredients->map(function (ReportRationIngredient $item) use ($totalQty) {
                $qty = (float) $item->quantity_per_unit;
                $lineTotal = $qty * (float) $item->price_per_unit;
                $percent = $totalQty > 0 ? round(($qty / $totalQty) * 100, 2) : 0;

                return [
                    'id' => $item->id,
                    'name' => $item->name,
                    'unit' => $item->unit,
                    'weight' => $qty,
                    'price_per_unit' => (int) $item->price_per_unit,
                    'total' => (int) round($lineTotal),
                    'percent' => $percent,
                    'load_percent' => $percent,
                ];
            })->values(),
            'feeding_meals' => $report->feedingMeals,
            'ration_weekly' => $report->rationWeekly,
            'attachments' => $report->attachments,
            'documents' => $report->documents,
            'report' => new ReportResource($report),
        ]);
    }

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

        $report = $this->latestApproved($project);
        $from = $request->date('from') ?? now()->subDays(7)->toDateString();
        $to = $request->date('to') ?? now()->toDateString();

        $logsQuery = ReportFeedingLog::query()
            ->where('project_id', $project->id)
            ->with(['feedingMeal', 'createdBy'])
            ->whereDate('fed_at_date', '>=', $from)
            ->whereDate('fed_at_date', '<=', $to)
            ->latest('fed_at_date')
            ->latest('id');

        $logs = $logsQuery->get();

        $schedule = collect($report?->feedingMeals ?? [])->map(function (ReportFeedingMeal $meal) use ($logs) {
            $mealLogs = $logs->filter(function (ReportFeedingLog $log) use ($meal) {
                return (int) $log->feeding_meal_id === (int) $meal->id
                    || ($log->meal_time && $log->meal_time === $meal->meal_time);
            })->values();

            return [
                'meal' => [
                    'id' => $meal->id,
                    'meal_time' => $meal->meal_time,
                    'name' => $meal->name,
                    'percent_of_total' => $meal->percent_of_total !== null ? (float) $meal->percent_of_total : null,
                ],
                'ingredients' => null,
                'photos_count' => $mealLogs->sum(fn (ReportFeedingLog $log) => is_array($log->photos) ? count($log->photos) : 0),
                'logs' => FeedingLogResource::collection($mealLogs),
            ];
        })->values();

        return ApiResponse::success([
            'project_id' => $project->id,
            'current_ration_id' => $report?->id,
            'ration_ingredients' => $report?->rationIngredients,
            'daily_plan' => $schedule,
            'logs' => FeedingLogResource::collection($logs),
            'from' => $from,
            'to' => $to,
        ]);
    }

    public function storeFeedingLog(StoreFeedingLogRequest $request, Project $project)
    {
        $this->authorizeProjectAccess($project);
        $this->assertCanManageRation($request->user(), $project);

        $data = $request->validated();
        $reportId = $data['report_id'] ?? $this->latestApproved($project)?->id;

        if (! empty($data['feeding_meal_id']) && empty($data['meal_time'])) {
            $meal = ReportFeedingMeal::query()->find($data['feeding_meal_id']);
            $data['meal_time'] = $meal?->meal_time;
            $data['meal_name'] = $data['meal_name'] ?? $meal?->name;
        }

        $log = ReportFeedingLog::create([
            'project_id' => $project->id,
            'report_id' => $reportId,
            'feeding_meal_id' => $data['feeding_meal_id'] ?? null,
            'fed_at_date' => $data['fed_at_date'],
            'meal_time' => $data['meal_time'] ?? null,
            'meal_name' => $data['meal_name'] ?? null,
            'photos' => $data['photos'] ?? null,
            'notes' => $data['notes'] ?? null,
            'created_by' => Auth::id(),
        ]);

        return ApiResponse::created(
            new FeedingLogResource($log->load(['feedingMeal', 'createdBy'])),
            'ثبت خوراک‌دهی انجام شد.'
        );
    }

    private function latestApproved(Project $project): ?Report
    {
        return Report::query()
            ->where('project_id', $project->id)
            ->where('type', Report::TYPE_RATION)
            ->where('workflow_status', Report::WORKFLOW_APPROVED)
            ->with($this->detailRelations())
            ->orderByDesc('report_at')
            ->orderByDesc('date')
            ->orderByDesc('id')
            ->first();
    }

    private function payload(RationReportRequest $request, bool $isDraft, ?Report $existing = null): array
    {
        $data = $request->safe()->except(['is_draft']);
        $data['type'] = Report::TYPE_RATION;
        $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;
        $data['create_invoice'] = $request->boolean('create_invoice');

        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,
                'group_name' => $item['group_name'] ?? null,
                'sort_order' => $index,
            ]);
        }
    }

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

        $report->rationIngredients()->delete();
        foreach ($ingredients as $index => $row) {
            ReportRationIngredient::create([
                'report_id' => $report->id,
                'name' => $row['name'],
                'unit' => $row['unit'] ?? null,
                'quantity_per_unit' => $row['quantity_per_unit'] ?? 0,
                'price_per_unit' => $row['price_per_unit'] ?? 0,
                'sort_order' => $index,
            ]);
        }
    }

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

        $report->feedingMeals()->delete();
        foreach ($meals as $index => $row) {
            ReportFeedingMeal::create([
                'report_id' => $report->id,
                'meal_time' => $row['meal_time'] ?? null,
                'name' => $row['name'] ?? null,
                'percent_of_total' => $row['percent_of_total'] ?? 0,
                'sort_order' => $index,
            ]);
        }
    }

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

        $report->rationWeekly()->delete();
        foreach ($weekly as $row) {
            ReportRationWeekly::create([
                'report_id' => $report->id,
                'day_of_week' => $row['day_of_week'],
                'is_active' => (bool) ($row['is_active'] ?? true),
                'status' => $row['status'] ?? ReportRationWeekly::STATUS_NORMAL,
            ]);
        }
    }

    private function detailRelations(): array
    {
        return [
            'project',
            'producer.user',
            'user',
            'product',
            'items.product',
            'rationIngredients',
            'feedingMeals',
            'rationWeekly',
        ];
    }

    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, IndexRationReportRequest $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->whereHas('items.product', function (Builder $q) use ($term): void {
                $q->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('ration_type')) {
            $query->where('ration_type', 'like', '%'.$request->string('ration_type').'%');
        }

        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 ensureRation(Report $report): void
    {
        if (! $report->isRationReport()) {
            abort(404, 'جیره یافت نشد.');
        }
    }

    private function assertCanManageRation($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' => 'ration_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' => 'ration_report_submitted',
                'report_id' => $report->id,
                'project_id' => $report->project_id,
            ]));
        });
    }
}
