<?php

namespace App\Services;

use App\Forms\CommonForm;
use App\Entities\CommonEntity;
use App\Models\CommonModel;
use App\Libraries\AuthContext;
use CodeIgniter\Database\Exceptions\DatabaseException;
use App\Exceptions\FormValidationException;
use RuntimeException;

abstract class CommonService
{
    private ?AuthContext $_authContext = null;
    private array $_classPaths = [];
    protected $title = null;

    protected ?object $_form = null;
    protected ?object $_helper = null;

    protected string $formClass = '';
    protected string $helperClass = '';

    protected function __construct(protected CommonModel $model)
    {
    }
    abstract public function getEntityClass(): string;

    /**
     * 공통 트랜잭션 래퍼
     */
    final protected function dbTransaction(callable $callback, string $functionName = ''): mixed
    {
        $db = \Config\Database::connect();
        try {
            $db->transException(true)->transStart();
            $result = $callback($db);
            $db->transComplete();
            return $result;
        } catch (FormValidationException $e) {
            $db->transRollback();
            throw $e; // ✅ 이거 필수
        } catch (DatabaseException $e) {
            $errorMessage = sprintf(
                "\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n",
                static::class . '->' . ($functionName ?: 'dbTransaction'),
                $this->model->getLastQuery() ?? "No Query Available",
                $e->getMessage()
            );
            log_message('error', $errorMessage);
            throw new RuntimeException($errorMessage, $e->getCode(), $e);
        } catch (\Throwable $e) {
            $db->transRollback();
            throw $e; // ✅ 여기서도 RuntimeException으로 감싸지 말 것 (권장)
        }
    }

    final public function getActionForm(): mixed
    {
        if ($this->_form === null && $this->formClass) {
            $this->_form = new $this->formClass();
            if (method_exists($this->_form, 'setAttributes')) {
                $this->_form->setAttributes([
                    'pk_field' => $this->getPKField(),
                    'title_field' => $this->getTitleField(),
                    'table' => $this->model->getTable(),
                    'useAutoIncrement' => $this->model->useAutoIncrement(),
                    'class_path' => $this->getClassPaths(false)
                ]);
            }
        }
        return $this->_form;
    }

    public function getHelper(): mixed
    {
        if ($this->_helper === null && $this->helperClass) {
            $this->_helper = new $this->helperClass();
            if (method_exists($this->_helper, 'setAttributes')) {
                $this->_helper->setAttributes([
                    'pk_field' => $this->getPKField(),
                    'title_field' => $this->getTitleField(),
                    'table' => $this->model->getTable(),
                    'useAutoIncrement' => $this->model->useAutoIncrement(),
                    'class_path' => $this->getClassPaths(false)
                ]);
            }
        }
        return $this->_helper;
    }
    final public function getAuthContext(): AuthContext
    {
        if ($this->_authContext === null) {
            $this->_authContext = new AuthContext();
        }
        return $this->_authContext;
    }
    final protected function addClassPaths(string $path): void
    {
        $this->_classPaths[] = $path;
    }
    final public function getPKField(): string
    {
        return $this->model->getPKField();
    }
    final public function getTitleField(): string
    {
        return $this->model->getTitleField();
    }

    final public function getClassPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string
    {
        return $isArray ? $this->_classPaths : implode($delimeter, $this->_classPaths);
    }
    final public function getNextPK(): int
    {
        $pkField = $this->getPKField();
        $row = $this->model->selectMax($pkField)->get()->getRow();
        return isset($row->{$pkField}) ? ((int) $row->{$pkField} + 1) : 1;
    }
    /**
     * 단일 엔티티를 조회합니다.
     */
    abstract protected function getEntity_process(CommonEntity $entity): CommonEntity;
    final public function getEntity($where = null, ?string $message = null): ?CommonEntity
    {
        try {
            $entity = null;
            if ($where !== null) {
                $entity = is_array($where) ? $this->model->where($where)->first() : $this->model->find($where);
            }
            if ($entity === null) {
                return null;
            }
            // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사
            $entityClass = $this->getEntityClass();
            if (!$entity instanceof $entityClass) {
                throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능:" . get_class($entity));
            }
            return $this->getEntity_process($entity);
        } catch (DatabaseException $e) {
            $errorMessage = sprintf(
                "\n------DB Query 오류 (%s)-----\nQuery: %s\nError: %s\n------------------------------\n",
                static::class . '->' . __FUNCTION__,
                $this->model->getLastQuery() ?? "No Query Available",
                $e->getMessage()
            );
            log_message('error', $errorMessage);
            throw new RuntimeException($errorMessage, $e->getCode(), $e);
        } catch (\Exception $e) {
            $errorMessage = sprintf(
                "\n------일반 오류 (%s)-----\nError: %s\n------------------------------\n",
                static::class . '->' . __FUNCTION__,
                $e->getMessage()
            );
            throw new \Exception($errorMessage, $e->getCode(), $e);
        }
    }
    /**
     * 엔티티 배열 조회합니다.
     * @return CommonEntity|null CommonEntity 인스턴스 또는 찾지 못했을 경우 null
     */
    protected function getEntities_process(mixed $where = null, array $columns = ['*'], array $entities = []): array
    {
        if ($where) {
            $this->model->where($where);
        }
        //기본 Order By 이용하기위해 
        $this->setOrderBy();
        /** @var array<\App\Entities\CommonEntity> $results */
        $results = $this->model->select(implode(',', $columns))->findAll();
        log_message('debug', $this->model->getLastQuery());
        foreach ($results as $entity) {
            $entities[] = $entity;
        }
        return $entities;
    }
    public function getEntities(?array $where = null, array $columns = ['*'], array $entities = []): array
    {
        try {
            /** @var array<\App\Entities\CommonEntity> $entities */
            $entities = $this->getEntities_process($where, $columns, $entities);
            return $entities;
        } catch (DatabaseException $e) {
            $errorMessage = sprintf(
                "\n------DB Query 오류 (%s)-----\nQuery: %s\nError: %s\n------------------------------\n",
                static::class . '->' . __FUNCTION__,
                $this->model->getLastQuery() ?? "No Query Available",
                $e->getMessage()
            );
            log_message('error', $errorMessage);
            throw new RuntimeException($errorMessage, $e->getCode(), $e);
        } catch (\Exception $e) {
            $errorMessage = sprintf(
                "\n------일반 오류 (%s)-----\nError: %s\n------------------------------\n",
                static::class . '->' . __FUNCTION__,
                $e->getMessage()
            );
            throw new \Exception($errorMessage, $e->getCode(), $e);
        }
    }
    //CURD 결과처리용
    protected function handle_save_result(mixed $result, int|string $uid): int|string
    {
        if ($result === false) {
            $errors = $this->model->errors();
            $errorMsg = is_array($errors) ? implode(", ", $errors) : "DB 저장 작업이 실패했습니다.";
            throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg);
        }
        $pk = $uid; // 기본적으로 기존 $uid (업데이트의 경우)
        // AUTO_INCREMENT 필드를 사용하는 경우, INSERT 작업이라면 새로 생성된 ID를 가져옵니다.
        // INSERT 작업은 보통 $uid가 0 또는 null/빈 문자열일 때 실행됩니다.
        if ($this->model->useAutoIncrement() && (empty($uid) || $uid === 0)) {
            // CodeIgniter 모델의 getInsertID()를 사용하여 새로 생성된 PK를 확실히 가져옵니다.
            $insertID = $this->model->getInsertID();
            if ($insertID > 0) {
                $pk = $insertID;
            }
        } elseif ($this->model->useAutoIncrement() && is_numeric($result) && (int) $result > 0) {
            // save()가 성공적인 INSERT 후 PK를 반환하는 경우를 대비 (CI4의 동작)
            $pk = (int) $result;
        }
        // 최종적으로 PK가 유효한지 확인합니다.
        if (empty($pk)) {
            $errors = $this->model->errors();
            $errorMsg = is_array($errors) && !empty($errors) ? implode(", ", $errors) : "DB 작업 성공 후 PK를 확인할 수 없거나 모델 오류 발생:{$pk}";
            throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg);
        }
        return $pk;
    }

    protected function save_process(CommonEntity $entity): CommonEntity
    {
        try {
            // INSERT 시 Entity의 PK는 0 또는 NULL이어야 함 (DB가 ID를 생성하도록)
            $initialPK = $entity->getPK();
            $result = $this->model->save($entity);
            log_message('debug', __FUNCTION__ . ":" . var_export($entity, true));
            log_message('debug', __FUNCTION__ . ":" . $this->model->getLastQuery());
            // 최종적으로 DB에 반영된 PK를 반환받습니다. (UPDATE이면 기존 PK, INSERT이면 새 PK)
            $entity->{$this->getPKField()} = $this->handle_save_result($result, $initialPK);
            // handle_save_result에서 확인된 최종 PK를 사용하여 DB에서 최신 엔티티를 가져옴
            return $entity;
        } catch (\Throwable $e) {
            log_message('debug', __FUNCTION__ . ":" . var_export($entity, true));
            log_message('debug', __FUNCTION__ . ":" . $this->model->getLastQuery());
            throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:" . $e->getMessage());
        }
    }

    //Action 작업시 field에따른 Hook처리(각 Service에서 override);
    protected function actionForm_fieldhook_process(string $field, $value, array $formDatas): array
    {
        return $formDatas;
    }

    //생성용
    protected function create_process(array $formDatas): CommonEntity
    {
        try {
            $actionForm = $this->getActionForm();
            if ($actionForm instanceof CommonForm) {
                $actionForm->action_init_process('create', $formDatas);
                // log_message('debug', 'BEFORE hook CREATE FORMDATA:' . print_r($formDatas ?? null, true));
                foreach ($formDatas as $field => $value) {
                    $formDatas = $this->actionForm_fieldhook_process($field, $value, $formDatas);
                }
                // log_message('debug', 'AFTER hook CREATE FORMDATA:' . print_r($formDatas ?? null, true));
                $actionForm->validate($formDatas);  // ✅ 여기서 검증
            }

            $entityClass = $this->getEntityClass();
            $entity = new $entityClass($formDatas);
            if (!$entity instanceof $entityClass) {
                throw new RuntimeException("Return Type은 {$entityClass}만 가능");
            }
            return $this->save_process($entity);
        } catch (FormValidationException $e) {
            throw $e; // ✅ 감싸지 말고 그대로
        } catch (\Throwable $e) {
            throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:" . $e->getMessage());
        }
    }

    final public function create(array $formDatas): CommonEntity
    {
        return $this->dbTransaction(function () use ($formDatas) {
            $formDatas['user_uid'] = $this->getAuthContext()->getUID();
            return $this->create_process($formDatas);
        }, __FUNCTION__);
    }

    //수정용
    protected function save_before_fill(array $formDatas): array
    {
        return $formDatas;
    }
    protected function modify_process($entity, array $formDatas): CommonEntity
    {
        try {
            $actionForm = $this->getActionForm();
            if ($actionForm instanceof CommonForm) {
                $actionForm->action_init_process('modify', $formDatas);
                // log_message('debug', 'BEFORE hook MODIFY FORMDATA:' . print_r($formDatas ?? null, true));
                foreach ($formDatas as $field => $value) {
                    $formDatas = $this->actionForm_fieldhook_process($field, $value, $formDatas);
                }
                // log_message('debug', 'AFTER hook MODIFY FORMDATA:' . print_r($formDatas ?? null, true));
                $actionForm->validate($formDatas);  // ✅ 여기서 검증
            }
            // 검증 통과 후 엔티티 반영
            $formDatas = $this->save_before_fill($formDatas);
            // log_message('debug', 'BEFORE MODIFY fill Entity:' . print_r($formDatas ?? null, true));
            $entity->fill($formDatas);
            // log_message('debug', 'AFTER MODIFY fill Entity:' . print_r($entity ?? null, true));
            if (!$entity->hasChanged()) {
                return $entity;
            }
            return $this->save_process($entity);
        } catch (FormValidationException $e) {
            throw $e; // ✅ 감싸지 말고 그대로
        } catch (\Throwable $e) {
            throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:" . $e->getMessage());
        }
    }

    final public function modify(string|int $uid, array $formDatas): CommonEntity
    {
        return $this->dbTransaction(function () use ($uid, $formDatas) {
            $entity = $this->getEntity($uid);
            if (!$entity) {
                throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다.");
            }
            $formDatas['user_uid'] = $this->getAuthContext()->getUID();
            return $this->modify_process($entity, $formDatas);
        }, __FUNCTION__);
    }

    //배치 작업용 수정
    protected function batchjob_process($entity, array $formDatas): CommonEntity
    {
        $entity = $this->modify_process($entity, $formDatas);
        return $entity;
    }
    final public function batchjob(array $uids, array $formDatas): array
    {
        return $this->dbTransaction(function () use ($uids, $formDatas) {
            $entities = [];
            foreach ($uids as $uid) {
                $entity = $this->getEntity($uid);
                if (!$entity) {
                    throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다.");
                }
                $entityClass = $this->getEntityClass();
                if (!$entity instanceof $entityClass) {
                    throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능");
                }
                $formDatas['user_uid'] = $this->getAuthContext()->getUID();
                $entities[] = $this->batchjob_process($entity, $formDatas);
            }
            return $entities;
        }, __FUNCTION__);
    }

    //삭제용 (일반)
    protected function delete_process($entity): CommonEntity
    {
        $result = $this->model->delete($entity->getPK());
        log_message('debug', $this->model->getLastQuery());
        if ($result === false) {
            $errors = $this->model->errors();
            $errorMsg = is_array($errors) ? implode(", ", $errors) : "삭제 작업이 실패했습니다.";
            throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg);
        }
        return $entity;
    }
    final public function delete(string|int $uid): CommonEntity
    {
        return $this->dbTransaction(function () use ($uid) {
            if (!$uid) {
                throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 삭제에 필요한 PrimaryKey 가 정의 되지 않았습니다.");
            }
            $entity = $this->getEntity($uid);
            if (!$entity) {
                throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다.");
            }
            $entityClass = $this->getEntityClass();
            if (!$entity instanceof $entityClass) {
                throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능");
            }
            return $this->delete_process($entity);
        }, __FUNCTION__);
    }

    //삭제용 (배치 작업)
    protected function batchjob_delete_process($entity): CommonEntity
    {
        $entity = $this->delete_process($entity);
        return $entity;
    }

    final public function batchjob_delete(array $uids): array
    {
        return $this->dbTransaction(function () use ($uids) {
            $entities = [];
            foreach ($uids as $uid) {
                $entity = $this->getEntity($uid);
                if (!$entity) {
                    throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다.");
                }
                $entityClass = $this->getEntityClass();
                if (!$entity instanceof $entityClass) {
                    throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능");
                }
                $entities[] = $this->batchjob_delete_process($entity);
            }
            return $entities;
        }, __FUNCTION__);
    }

    //Index용
    final public function getTotalCount(): int
    {
        return $this->model->countAllResults();
    }
    //Limit처리
    final public function setLimit(int $perpage): void
    {
        $this->model->limit($perpage);
    }
    //Offset처리
    final public function setOffset(int $offset): void
    {
        $this->model->offset($offset);
    }
    public function setFilter(string $field, mixed $filter_value): void
    {
        switch ($field) {
            default:
                $this->model->where("{$this->model->getTable()}.{$field}", $filter_value);
                break;
        }
    }
    //검색어조건절처리
    public function setSearchWord(string $word): void
    {
        $this->model->orLike($this->model->getTable() . "." . $this->getTitleField(), $word, 'both');
    }
    //날자검색
    public function setDateFilter(string $start, string $end): void
    {
        $this->model->where(sprintf("%s.created_at >= '%s 00:00:00'", $this->model->getTable(), $start));
        $this->model->where(sprintf("%s.created_at <= '%s 23:59:59'", $this->model->getTable(), $end));
    }
    //OrderBy 처리
    public function setOrderBy(mixed $field = null, mixed $value = null): void
    {
        if ($field !== null && $value !== null) {
            $this->model->orderBy(sprintf("%s.%s %s", $this->model->getTable(), $field, $value));
        } else {
            $this->model->orderBy(sprintf("%s.%s %s", $this->model->getTable(), $this->getPKField(), "DESC"));
        }
    }
}
