<?php

namespace App\Repositories;

use App\Repositories\Contracts\WriteRepositoryInterface;
use Illuminate\Cache\CacheManager;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;

abstract class WriteRepository implements WriteRepositoryInterface
{
    protected Model $model;

    protected CacheManager $cache;

    protected string $cacheTag;

    protected string $readRepositoryClass;

    public function __construct(Model $model, CacheManager $cache)
    {
        $this->model = $model;
        $this->cache = $cache;
        $this->cacheTag = $this->getCacheTag();
        $this->readRepositoryClass = $this->getReadRepositoryClass();
    }

    abstract protected function getCacheTag(): string;

    abstract protected function getReadRepositoryClass(): string;

    protected function getReadRepository(): mixed
    {
        return app($this->readRepositoryClass);
    }

    protected function clearRelatedCache(Model $model): void
    {
        $this->getReadRepository()->clearCacheForModel($model);
    }

    protected function clearCollectionCache(Collection $models): void
    {
        foreach ($models as $model) {
            $this->clearRelatedCache($model);
        }
    }

    public function create(array $data): Model
    {
        $model = $this->model->create($data);
        $this->clearRelatedCache($model);

        return $model;
    }

    public function createMany(array $data): Collection
    {
        $models = $this->model->insert($data) ? $this->model->whereIn('id',
            $this->model->latest()->take(count($data))->pluck('id')
        )->get() : collect();

        $this->getReadRepository()->clearCache();

        return $models;
    }

    public function update(Model $model, array $data): bool
    {
        $result = $model->update($data);
        $this->clearRelatedCache($model);

        return $result;
    }

    public function updateById(int $id, array $data): bool
    {
        $model = $this->model->findOrFail($id);

        return $this->update($model, $data);
    }

    public function upsert(array $data, array|string $uniqueBy, ?array $update = null): Collection
    {
        $this->model->upsert($data, $uniqueBy, $update);

        // Clear all cache since upsert can affect multiple records
        $this->getReadRepository()->clearCache();

        // Return the upserted records
        $uniqueValues = collect($data)->pluck($uniqueBy);

        return $this->model->whereIn($uniqueBy, $uniqueValues)->get();
    }

    public function delete(Model $model): bool
    {
        $result = $model->delete();
        $this->clearRelatedCache($model);

        return $result;
    }

    public function deleteById(int $id): bool
    {
        $model = $this->model->findOrFail($id);

        return $this->delete($model);
    }

    public function deleteMultiple(array $ids): int
    {
        $deleted = $this->model->whereIn('id', $ids)->delete();

        // Clear all cache since multiple records are deleted
        $this->getReadRepository()->clearCache();

        return $deleted;
    }

    public function restore(Model $model): bool
    {
        $result = $model->restore();
        $this->clearRelatedCache($model);

        return $result;
    }

    public function restoreById(int $id): bool
    {
        $model = $this->model->withTrashed()->findOrFail($id);

        return $this->restore($model);
    }

    public function forceDelete(Model $model): bool
    {
        $result = $model->forceDelete();
        $this->clearRelatedCache($model);

        return $result;
    }

    public function forceDeleteById(int $id): bool
    {
        $model = $this->model->withTrashed()->findOrFail($id);

        return $this->forceDelete($model);
    }

    public function sync(Model $model, string $relation, array $ids, bool $detaching = true): array
    {
        $result = $model->{$relation}()->sync($ids, $detaching);
        $this->clearRelatedCache($model);

        return $result;
    }

    public function attach(Model $model, string $relation, array $ids, array $attributes = [], bool $touch = true): void
    {
        $model->{$relation}()->attach($ids, $attributes, $touch);
        $this->clearRelatedCache($model);
    }

    public function detach(Model $model, string $relation, ?array $ids = null, bool $touch = true): int
    {
        $result = $model->{$relation}()->detach($ids, $touch);
        $this->clearRelatedCache($model);

        return $result;
    }

    public function clearCache(): void
    {
        $this->getReadRepository()->clearCache();
    }

    public function clearCacheForModel(Model $model): void
    {
        $this->clearRelatedCache($model);
    }

    public function clearCacheForCollection(Collection $models): void
    {
        $this->clearCollectionCache($models);
    }
}
