<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\Rating\StoreRatingRequest;
use App\Http\Requests\Rating\UpdateRatingRequest;
use App\Http\Resources\RatingResource;
use App\Models\Product;
use App\Models\Rating;
use App\Support\ApiResponse;
use App\Support\MorphTypeResolver;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use InvalidArgumentException;

class RatingController extends Controller
{
  public function index(Request $request)
  {
    Gate::authorize('viewAny', Rating::class);

    if (! $request->filled('rateable_type') || ! $request->filled('rateable_id')) {
      return ApiResponse::response(message: 'rateable_type و rateable_id الزامی هستند.', status: 422);
    }

    try {
      $rateableType = MorphTypeResolver::resolve($request->string('rateable_type'));
    } catch (InvalidArgumentException $exception) {
      return ApiResponse::response(message: $exception->getMessage(), status: 422);
    }

    $query = Rating::query()
      ->with('user')
      ->where('rateable_type', $rateableType)
      ->where('rateable_id', $request->integer('rateable_id'))
      ->latest();

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

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

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

    try {
      $rateableType = MorphTypeResolver::resolve($request->string('rateable_type'));
      $rateableId = $request->integer('rateable_id');

      $rateableClass = $rateableType;
      $rateableClass::query()->findOrFail($rateableId);

      DB::beginTransaction();

      $rating = Rating::updateOrCreate(
        [
          'user_id' => Auth::id(),
          'rateable_type' => $rateableType,
          'rateable_id' => $rateableId,
        ],
        [
          'score' => $request->scoreValue(),
          'comment' => $request->input('comment'),
        ]
      );

      $this->syncRateableStats($rateableType, $rateableId);

      DB::commit();
    } catch (InvalidArgumentException $exception) {
      return ApiResponse::response(message: $exception->getMessage(), status: 422);
    } catch (Exception $exception) {
      DB::rollBack();

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

    return ApiResponse::created(
      new RatingResource($rating->load('user')),
      'امتیاز با موفقیت ثبت شد.'
    );
  }

  public function show(Rating $rating)
  {
    Gate::authorize('view', $rating);

    return new RatingResource($rating->load('user'));
  }

  public function update(UpdateRatingRequest $request, Rating $rating)
  {
    Gate::authorize('update', $rating);

    try {
      DB::beginTransaction();

      $data = array_filter([
        'comment' => $request->input('comment'),
      ], fn ($value) => $value !== null);

      if ($request->scoreValue() !== null) {
        $data['score'] = $request->scoreValue();
      }

      $rating->update($data);
      $this->syncRateableStats($rating->rateable_type, (int) $rating->rateable_id);

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

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

    return new RatingResource($rating->fresh()->load('user'));
  }

  public function destroy(Rating $rating)
  {
    Gate::authorize('delete', $rating);

    try {
      DB::beginTransaction();

      $rateableType = $rating->rateable_type;
      $rateableId = (int) $rating->rateable_id;

      $rating->delete();
      $this->syncRateableStats($rateableType, $rateableId);

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

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

    return ApiResponse::success(message: 'با موفقیت حذف شد');
  }

  private function syncRateableStats(string $rateableType, int $rateableId): void
  {
    if ($rateableType !== Product::class) {
      return;
    }

    $product = Product::query()->find($rateableId);

    if (! $product) {
      return;
    }

    $average = Rating::query()
      ->where('rateable_type', Product::class)
      ->where('rateable_id', $rateableId)
      ->avg('score');

    $count = Rating::query()
      ->where('rateable_type', Product::class)
      ->where('rateable_id', $rateableId)
      ->count();

    $product->update([
      'rating_average' => $average !== null ? round((float) $average, 2) : null,
      'rating_count' => $count,
    ]);
  }
}
