<?php

namespace App\Repositories\Eloquent;

use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
use App\Repositories\Interfaces\BaseRepositoryInterface;

class BaseRepository implements BaseRepositoryInterface
{
    /**
     * @var Model
     */
     protected $model;

    /**
     * BaseRepositoryEloquent constructor.
     *
     * @param Model $model
     */
    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    /**
    * @return Collection
    */
    public function all(): Collection
    {
        return $this->model->all();
    }

    /**
    * @param array $attributes
    *
    * @return Model
    */
    public function create(array $attributes): Model
    {
        return $this->model->create($attributes);
    }

    /**
    * @param array $attributes
    *
    * @return Boolean
    */
    public function bulkInsert(array $attributes): bool
    {
        return $this->model->insert($attributes);
    }

    /**
    * @param int $id
    * @return Model
    */
    public function find(int $id): ?Model
    {
        return $this->model->findOrFail($id);
    }

    /**
    * @param array $attributes
    *
    * @return Model
    */
    public function update(array $attributes, $id): Model
    {
        $record = $this->find($id);
        $record->update($attributes);
        return $record;
    }

    /**
    * @param array $attributes
    * @param int $id
    *
    * @return Model
    */
    public function fill(array $attributes, int $id): Model
    {
        $record = $this->find($id);
        $record->fill($attributes);
        return $record;
    }

    /**
    * @param $id
    *
    * @return Boolean
    */
    public function delete($id)
    {
        $record = $this->find($id);
        return $record->delete($id);
    }

    /**
    * @param string $column
    * @param string $value
    * @return Model
    */
    public function getByColumn(string $column, string $value): ?Model
    {
        return $this->model->where($column, $value)->first();
    }

    /**
    * @param string $column
    * @param string $value
    * @return Collection
    */
    public function getMultipleValues(string $column, string $value): ?Collection
    {
        return $this->model->where($column, $value)->get();
    }

    /**
    * @param string $column
    * @return Boolean
    */
    public function deleteByColumn(string $column, string $value): bool
    {
        return $this->model->where($column, $value)->delete();
    }

    /**
     * @param array $filters
     * @param string $tableName
     *
     * @return Collection
     */
    public function search(array $filters, string $tableName): Collection
    {
        try {
            $columns = DB::connection()
                ->getSchemaBuilder()
                ->getColumnListing($tableName);
            if ($columns) {
                $columns = array_diff($columns, ['updated_at', 'deleted_at']);
            }
            $relationshipName = null;
            $query = $this->model->newQuery();
            $query->select($columns);
            foreach ($filters as $filter) {
                $key = $filter['key'] ?? null;
                $condition = $filter['condition'] ?? "=";
                $value = $filter['value'] ?? null;
                if (array_key_exists('key', $filter) && in_array($key, $columns)) {
                    switch ($condition) {
                        case '=':
                        case '!=':
                        case '>':
                        case '<':
                        case '>=':
                        case '<=':
                            $query->where($key, $condition, $value);
                            break;
                        case 'between':
                            $query->whereBetween($key, $value);
                            break;
                        case 'like':
                            $query->whereLike($key, $value);
                            break;
                        case 'in':
                            $query->whereIn($key, $value);
                            break;
                        default:
                            throw new Exception("Invalid condition: {$condition}");
                    }
                } else if (array_key_exists('pageInfo', $filter)) {
                    $this->setUpPagination($query, $filter);
                } else if (array_key_exists('sortInfo', $filter)) {
                    $this->setUpSorting($query, $filter, $columns);
                } else if (array_key_exists('withRelationship', $filter)) {
                    $relationshipName = $filter['withRelationship']['name'] ?? null;
                } else {
                    throw new Exception("Invalid key filter: {$key}");
                    app('log')->error("Invalid key filter", ['filter' => $filter]);
                }
            }
            if ($relationshipName) {
                try {
                    return $query->with($relationshipName)->get();
                } catch (Exception $e) {
                    app('log')->error("Invalid relationship", ['relationship' => $relationshipName]);
                    throw new Exception("Invalid relationship name");
                }
            } else {
                return $query->get();
            }
        } catch (Exception $e) {
            app('log')->error("Error", ['errorMessage' => $e->getMessage()]);
            throw new Exception($e->getMessage());
        }
    }

    /**
     * @param object $query
     * @param array $filters
     * @param array $allowedColumnsToFilter
     * 
     * @return void
     */
    public function processFilters(object $query, array $filters, array $allowedColumnsToFilter): void
    {
        $specialConditions = [
            
        ];
        
        foreach ($filters as $filter) {
            $key = $filter['key'] ?? null;
            $condition = $filter['condition'] ?? null;
            $value = $filter['value'] ?? null;
            if ($key) {
                $referenceKey = $this->getReferenceKey($key);
            }
            if (array_key_exists('key', $filter) && in_array($key, $allowedColumnsToFilter)) {
                switch ($condition) {
                    case '=':
                    case '!=':
                    case '>':
                    case '<':
                    case '>=':
                    case '<=':
                        if (in_array($key, $specialConditions)) {
                            $query->having($referenceKey, $condition, $value);
                        } else {
                            $query->where($referenceKey, $condition, $value);
                        }
                        break;
                    case 'between':
                        if (in_array($key, $specialConditions)) {
                            $query->having($referenceKey, $condition, $value);
                        } else {
                            $query->whereBetween($referenceKey, $value);
                        }
                        break;
                    case 'like':
                        $query->where($referenceKey, 'LIKE', '%' . $value . '%' ); 
                        break;
                    case 'in':
                        $query->whereIn($referenceKey, $value);
                        break;
                    case 'notIn':
                        $query->whereNotIn($referenceKey, $value);
                        break;
                    default:
                        throw new Exception("Invalid condition: {$condition}");
                }
            } else if (array_key_exists('pageInfo', $filter)) {
                $this->setUpPagination($query, $filter);
            } else if (array_key_exists('sortInfo', $filter)) {
                $this->setUpSorting($query, $filter, $allowedColumnsToFilter);
            } else {
                throw new Exception("Invalid key filter: {$key}");
                app('log')->error("Invalid key filter", ['filter' => $filter]);
            }
        }
    }

    /**
     * @param object $query
     * @param array $filter
     *
     * @return void
     */
    private function setUpPagination(object $query, array $filter): void
    {
        $page = (int) $filter['pageInfo']['page'];
        $limit = (int) $filter['pageInfo']['limit'];
        $offset = ($page - 1) * $limit;
        $query->offset($offset)->limit($limit);
    }

    /**
     * @param object $query
     * @param array $filter
     * @param array $columns
     *
     * @return void
     */
    private function setUpSorting(object $query, array $filter, array $columns): void
    {
        $sortKey = $filter['sortInfo']['key'];
        $referenceKey = $this->getReferenceKey($sortKey);
        if ($referenceKey) {
            $sortKey = $referenceKey;
            $columns[] = $referenceKey;
        }
        if (in_array($sortKey, $columns)) {
            $sortOrder = $filter['sortInfo']['order'] ?? 'asc';
            $query->orderBy($sortKey, strtolower($sortOrder));
        } else {
            throw new Exception("Invalid sort key: {$sortKey}");
            app('log')->error("Invalid sort key", ['sortKey' => $sortKey]);
        }
    }

    /**
     * @param string $key
     * 
     * @return string
     */
    private function getReferenceKey(string $key): string
    {
        return match ($key) {
            'professional' => 'profiles.full_name',
            'profession_id' => 'professionals.profession_id',
            'professional_id' => 'professionals.id',
            'professional_uuid' => 'professionals.professional_uuid',
            'ratings' => 'rate_ave',
            default => null,
        };
    }
}