<?php

namespace App\Core\Repository;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

use App\Models\Users;

use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;

abstract class EleqouentAbstractRepository
{
    protected $defaultModel = null;

    public function __construct()
    {

    }


    protected function find(Model $model, $id, $param = null, $column = ["*"])
    {
        $param = $param ? $param : [];
        $param["criteria"][] = ["field" => "id", "condition" => "=", "value" => $id];
        $param["firstRow"] = true;

        return $this->findByCriteria($model, $param, $column);
    }

    protected function all(Model $model)
    {
        return $this->findByCriteria($model);
    }

    private function withCriteria($get, $param)
    {
        foreach ($param as $key => $value) {
            $get->with([$value['method'] => function ($query) use ($value) {

                if (isset($value['orderBy'])) {
                    foreach ($value['orderBy'] as $orderByKey => $orderByValue) {
                        $query->orderBy($orderByValue['field'], $orderByValue['value']);
                    }
                }

                if (isset($value['field'])) {
                    foreach ($value['field'] as $fieldValue) {
                        $query->where($fieldValue['field'], $fieldValue['condition'], $fieldValue['value']);
                    }
                }

                if (isset($value['whereIn'])) {
                    foreach ($value['whereIn'] as $fieldValue) {
                        $query->whereIn($fieldValue['field'], $fieldValue['value']);
                    }
                }

            }]);
        }
        return $get;
    }

    private function withBuild(Builder $builder, array $withParams)
    {

        foreach ($withParams as $perParamKey => $perParamValue) {
            if (!is_array($perParamValue)) {
                $builder->with($perParamValue);
                continue;
            } else {
                $builder->with($perParamKey);
            }

            if (isset($perParamValue["with"])) {
                $builder = $this->withBuild($builder, $perParamValue);
            }

        }

        return $builder;

    }

    protected function findByCriteria(Model $model, $param = null, $column = ['*'])
    {
        try {
            if (isset($param["removeAppends"])) {
                $model->removeAppends($param["removeAppends"]);
            } else {
                $model->removeAppends([]);
            }

            if (isset($param["addAppends"])) {
                $model->addAppends($param["addAppends"]);
            } else {
                $model->addAppends([]);
            }

            $get = $model->on('mysql');


            if (isset($param["selectRaw"])) {
                $get = $get->select(DB::raw($param["selectRaw"]));
            }

            if (isset($param["criteria"])) {
                $get->where(
                    function ($query) use ($param) {
                        if (isset($param['whereOr'])) {
                            foreach ($param['whereOr'] as $key => $value) {
                                $query->whereOr($value['field'], $value['condition'], $value['value']);
                            }
                        }

                        if (isset($param['criteria'])) {
                            foreach ($param['criteria'] as $key => $value) {
                                $query->where($value['field'], $value['condition'], $value['value']);
                            }
                        }
                    }
                );
            }

            if (isset($param['criteriaWhere'])) {
                $whereType = fillOnUndefined($param['criteriaWhere'], 'whereType', 'where');
                $get->$whereType (
                    function ($query) use ($param) {
                        foreach ($param['criteriaWhere'] as $criteria) {
                            $whereType = $criteria['whereType'];
                            $query->$whereType(
                                function ($q) use ($criteria) {
                                    foreach ($criteria['params'] as $key => $value) {
                                        if (isset($value['where'])) {
                                            if ($value['where'] == 'OR') {
                                                $q->whereOr($value['field'], $value['condition'], $value['value']);
                                            } else {
                                                $q->where($value['field'], $value['condition'], $value['value']);
                                            }
                                        }
                                    }
                                }
                            );
                        }
                    }
                );
            }

            if (isset($param["criteriaWhereRaw"])) {
                $get->where(
                    function ($query) use ($param) {
                        if (isset($param['criteriaWhereRaw'])) {
                            foreach ($param['criteriaWhereRaw'] as $key => $value) {
                                $query->whereRaw($value['query'], $value['binds']);
                            }
                        }
                    }
                );
            }


            if (isset($param["whereOr"])) {
                $get->where(
                    function ($query) use ($param) {
                        if (isset($param['whereOr'])) {
                            foreach ($param['whereOr'] as $key => $value) {
                                $query->orWhere($value['field'], $value['condition'], $value['value']);
                            }
                        }

                    }
                );
            }

            if (isset($param['whereIn'])) {
                foreach ($param['whereIn'] as $whereInKey => $whereInValue) {
                    $get->whereIn($whereInValue['field'], $whereInValue['value']);
                }
            }


            if (isset($param['whereNotIn'])) {
                foreach ($param['whereNotIn'] as $whereInKey => $whereInValue) {
                    $get->whereNotIn($whereInValue['field'], $whereInValue['value']);
                }
            }


            if (isset($param['has'])) {
                foreach ($param["has"] as $perHas) {
                    $get->has($perHas["title"], $perHas["condition"], $perHas["value"]);
                }
            }

            if (isset($param['whereHas'])) {
                foreach ($param['whereHas'] as $key => $value) {
                    $get->whereHas($value['title'], function ($get) use ($value) {
                        foreach ($value['criteria']['param'] as $criteria) {
                            $queryType = $value['criteria']['type'];

                            if ($queryType == 'whereIn') {
                                $get->whereIn($criteria['field'], $criteria['value']);
                            } else {
                                $get->$queryType($criteria['field'], $criteria['condition'], $criteria['value']);
                            }
                        }
                    });
                }
            }

            /*Ex With Algorithm*/
            if (isset($param['with'])) {
                foreach ($param['with'] as $withKey => $withValue) {
                    $get->with($withValue);
                }
            }

            if (isset($param['withCriteria'])) {
                $get = $this->withCriteria($get, $param['withCriteria']);
            }

            if (isset($param['orderBy'])) {
                foreach ($param['orderBy'] as $orderByKey => $orderByValue) {
                    $get->orderBy($orderByValue['field'], $orderByValue['value']);
                }
            }

            if (isset($param['groupBy'])) {
                foreach ($param['groupBy'] as $perOrderBy) {
                    $get->groupBy($perOrderBy);
                }
            }

            if (isset($param['skip'])) {
                $get->skip($param['skip']);
            }

            if (isset($param['take'])) {
                $get->take($param['take']);
            }

            if (isset($param['sum'])) {
                return $get->sum($param['sum']);
            }

            if (isset($param['count'])) {
                return $get->count();
            }

            if (isset($param["toSql"])) {
                return $get->toSql();
            }
            $get = isset($param["firstRow"]) ? $get->first($column) : $get->get($column);
            if (isset($param['keyBy'])) {
                $get = $get->keyBy($param['keyBy']);
            }
            if (isset($param["getRaw"])) {
                return $get;
            }

            return ($get) ? $get->toArray() : [];

        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }

    protected function update(Model $model, $id, $param)
    {
        try {
            $update = $model->where('id', $id)->update($param);

            $update = $update ? $this->find($model, $id) : false;

            return output(['data' => $update]);
        } catch (Exception $e) {
            $message = $e->getMessage();

            return output(['status' => -1, 'message' => $message]);
        }

    }

    protected function updateWhere(Model $model, $value, $param)
    {
        try {
            $update = $model->where(
                function ($query) use ($value) {
                    if (isset($value['criteria'])) {
                        foreach ($value['criteria'] as $key => $value) {
                            $query->where($value['field'], $value['condition'], $value['value']);
                        }
                    }
                }
            );

            if (isset($value['orderBy'])) {
                foreach ($value['orderBy'] as $orderByKey => $orderByValue) {
                    $update->orderBy($orderByValue['field'], $orderByValue['value']);
                }
            }

            $update->first();
            $update = $update->update($param);

            $update = $update ? true : false;

            return output(['data' => $update]);
        } catch (Exception $e) {
            $message = $e->getTraceAsString();

            return output(['status' => -1, 'message' => $message]);
        }

    }

    protected function updateWhereIn(Model $model, $id, $param)
    {
        try {
            $update = $model->whereIn('id', $id)->update($param);
            $update = $update ? $update : false;
            return output(['data' => $update]);
        } catch (Exception $e) {
            $message = $e->getMessage();
            return output(['status' => -1, 'message' => $message]);
        }
    }

    protected function updateWhereBulk(Model $model, $value, $param)
    {
        try {
            $update = $model->where(
                function ($query) use ($value) {
                    if (isset($value['criteria'])) {
                        foreach ($value['criteria'] as $key => $value) {
                            $query->where($value['field'], $value['condition'], $value['value']);
                        }
                    }
                }
            );

            if (isset($value['orderBy'])) {
                foreach ($value['orderBy'] as $orderByKey => $orderByValue) {
                    $update->orderBy($orderByValue['field'], $orderByValue['value']);
                }
            }

            $update = $update->update($param);

            $update = $update ? true : false;

            return output(['data' => $update]);
        } catch (Exception $e) {
            $message = $e->getTraceAsString();

            return output(['status' => -1, 'message' => $message]);
        }

    }

    protected function create(Model $model, $data)
    {
        try {
            $create = $model->create($data);

            return output(['data' => $create->toArray()]);
        } catch (Exception $e) {
            $message = $e->getMessage();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }

    protected function insert(Model $model, $data)
    {
        try {
            $create = $model->insert($data);

            return output(['data' => $create]);
        } catch (QueryException $e) {
            $message = $e->getMessage();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        } catch (Exception $e) {
            $message = $e->getMessage();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }

    protected function createAll(Model $model, $data)
    {
        try {
            $create = [];
            foreach ($data as $perData) {
                $result = $this->create($model, $perData);

                if ($result["status"] != "success") {
                    throw new Exception($result["message"]);
                }

                $create[] = $result["data"];
            }

            return output(['data' => $create]);
        } catch (Exception $e) {
            $message = $e->getTraceAsString();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }

    protected function updateOrCreate(Model $model, array $criteria, array $saveData)
    {
        try {


            $findRow = $this->findByCriteria
            (
                $model,
                ["criteria" => $criteria, "firstRow" => 1, "getRaw" => 1]
            );



            $model->on("mysql");
            if ($findRow) {
                $result = $this->update($model, $findRow["id"], $saveData);
            } else {
                $result = $this->create($model, $saveData);
            }

            return $result;
        } catch (Exception $e) {
            $message = $e->getTraceAsString();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }

    protected function delete(Model $model, array $param)
    {
        try {
            $param["removeAppends"] = ["*"];
            $param["with"] = [];
            $deleteRows = $this->findByCriteria($model, $param, ["id"]);

            if (!$deleteRows) {
                return null;
            }
            $deleteRows = singleElementArray($deleteRows);
            $deleteIds = pickItemFromArray("id", $deleteRows);
            $result = $model->whereIn("id", $deleteIds);
            if ($result->count() > 0) {
                $deletedRows = $result->get()->toArray();
                if ($result->delete()) {
                    return $deletedRows;
                }
            }
            return null;
        } catch (Exception $e) {
            $message = $e->getTraceAsString();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }

    protected function destroy(Model $model, array $param)
    {
        try {

            if (!$param) {
                return null;
            }

            $deletedRows = $model->destroy($param);

            return output(['data' => $deletedRows]);;
        } catch (Exception $e) {
            $message = $e->getTraceAsString();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }


    //Todo: Bu fonksiyon iyileştirilecek
    protected function deleteById(Model $model, $id)
    {
        try {
            $result = $model->whereIn("id", $id);
            if ($result->count() > 0) {
                $deletedRows = $result->get()->toArray();
                if ($result->delete()) {
                    return $deletedRows;
                }
            }
            return null;
        } catch (Exception $e) {
            $message = $e->getTraceAsString();
            Log::error($message);
            return output(['status' => -1, 'message' => $message]);
        }
    }

    public function __call($method, $args)
    {
        try {
            if (method_exists($this, $method)) {
                array_unshift($args, $this->defaultModel);
                return call_user_func_array(array(&$this, $method), $args);
            } else {
                throw new Exception("'" . __CLASS__ . "' does not have '" . $method . "' method", 1);
            }
        } catch (Exception $e) {
            die($e->getMessage());
        }
    }

}
