<?php

namespace app\common\arw\adjfut\src;

use think\Collection;
use think\db\Query;
use think\facade\Db;
use think\helper\Arr;

/**
 * 分割数据表
 *
 * @date 2022-03-24
 * @example
 * @author arw
 * @since 1.0.0
 */
class PartitionTable
{
    /**
     * 源表名
     *
     * @var string
     */
    private $originTable = null;
    /**
     * 目标表名
     *
     * @var string
     */
    private $targetTable = null;
    /**
     * 唯一字段
     *
     * @var string
     */
    private $uniqueField = null;
    /**
     * 查询条件
     *
     * @var think\Db
     */
    private $query = null;
    /**
     * 是否锁表
     *
     * @var boolean|string
     */
    private $lockTable = false;
    /**
     * 初始化
     *
     * @param string $table 原数据表
     * @param string $suffix 分割表后缀
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function __construct(string $table, string $suffix)
    {
        $this->originTable = $table;
        $this->targetTable = join('_', [$table, $suffix]);

        $this->checkTargetTable();
    }

    /**
     * 是否锁表
     *
     * @param boolean|string $lock
     * @return self
     * @date 2023-01-04
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function setLockTable($lock): self
    {
        $this->lockTable = $lock;
        return $this;
    }

    /**
     * 设置唯一字段（设置后将删除原表数据）
     *
     * @param string $uniqueField
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function setUniqueField(string $uniqueField): self
    {
        $this->uniqueField = $uniqueField;
        return $this;
    }

    /**
     * 设置查询条件
     *
     * @param callable $cb
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function setQuery(callable $cb): self
    {
        $this->query = call_user_func($cb, $this->getOriginDb());
        return $this;
    }

    /**
     * 跨月查询
     *
     * @param string $table
     * @param string $field 字段
     * @param array $option
     * @param array $option[start] 开始时间 Y-m-d H:i:s
     * @param array $option[end] 结束时间 Y-m-d H:i:s
     * @date 2022-04-06
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function queryForMonth(string $table, string $field, array $option): Query
    {
        $start = Arr::get($option, 'start');
        $end = Arr::get($option, 'end');
        if (!$start || !$end) {
            throw new \Exception("缺少时间限制", 1);
        }
        $result = Db::query("SHOW TABLES LIKE '${table}_%'");
        $tables = [];
        foreach ($result as $value) {
            $tables = array_merge($tables, array_values($value));
        }
        $startYm = date('Ym', strtotime($start));
        $endYm = date('Ym', strtotime($end));
        $months = [];
        $months[] = $startYm;

        if ($startYm != $endYm) {
            $current = $startYm;
            while (true) {
                $current = date('Ym', strtotime($current . "01 +1month"));
                $months[] = $current;
                if ($current === $endYm) {
                    break;
                }
            }
            // $months[] = $endYm;
        }

        $temp = array_filter($tables, function ($v) use ($table, $months) {
            return in_array(explode("${table}_", $v)[1], $months);
        });

        $query = Db::table(in_array(date('Ym'), $months) ? $table : array_shift($temp));

        $query->union(array_map(function ($v) {
            return Db::name($v)->fetchSql()->select();
        }, $temp));

        return Db::table(join(' ', [
            $query->buildSql(),
            $table
        ]))->whereBetweenTime($field, $start, $end);
    }

    /**
     * 迁移数据
     *
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    public function save()
    {
        Db::startTrans();
        try {
            $lockTable = $this->lockTable;
            if ($lockTable) {
                $this->getOriginDb()->lock($lockTable)->select();
                $this->getTargetDb()->lock($lockTable)->select();
            }

            $target = $this->targetTable;
            $originSql = $this->query->fetchSql()->select();
            $result = Db::execute("REPLACE INTO `$target` $originSql;");
            if ($result === false) {
                throw new \Exception("迁移数据失败", 1);
            }

            $uniqueField = $this->uniqueField;
            if ($uniqueField) {
                $this->getTargetDb()->field([
                    $uniqueField
                ])->chunk(500, function (Collection $ids) use ($uniqueField) {
                    $this->getOriginDb()->whereIn(
                        $uniqueField,
                        $ids->column($uniqueField)
                    )->delete();
                }, $uniqueField);
            }
            Db::commit();
        } catch (\Throwable $th) {
            Db::rollback();
            throw $th;
        }
    }

    /**
     * 获取原始表
     *
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    private function getOriginDb(): Query
    {
        return Db::name($this->originTable);
    }

    /**
     * 获取目标表
     *
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    private function getTargetDb(): Query
    {
        return Db::name($this->targetTable);
    }

    /**
     * 检查目标表
     *
     * @return void
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    private function checkTargetTable(): void
    {
        if (!in_array($this->targetTable, Db::getTables())) {
            $origin = $this->originTable;
            $target = $this->targetTable;
            $result = Db::execute("CREATE TABLE `$target` LIKE `$origin`");
            if ($result === false) {
                throw new \Exception("创建迁移表失败", 1);
            }
        }
    }
}
