<?php

declare(strict_types=1);

namespace App\Service;

use Carbon\Carbon;
use Hyperf\DbConnection\Db;

/**
 * Dashboard 统计服务
 *
 * 封装 overview / trend / breakdown 三类聚合查询，
 * 使用原生 SQL 提高聚合效率，参数绑定防注入。
 */
class DashboardStatsService
{
    /**
     * data_type => 实体表映射
     */
    private const TYPE_TABLE_MAP = [
        'order'   => 'orders',
        'product' => 'products',
        'refund'  => 'refunds',
    ];

    /**
     * 允许的 data_type 值（含 inventory，仅统计失败数）
     */
    public const VALID_DATA_TYPES = ['order', 'product', 'refund', 'inventory'];

    /**
     * 允许的 group_by 值
     */
    public const VALID_GROUP_BY = ['day', 'week', 'month'];

    /**
     * 允许的 breakdown dimension 值
     */
    public const VALID_DIMENSIONS = ['company', 'platform', 'store'];

    /**
     * dimension => 表名和名称字段映射
     */
    private const DIMENSION_TABLE_MAP = [
        'company'  => ['table' => 'companies', 'name_field' => 'name'],
        'platform' => ['table' => 'platforms', 'name_field' => 'name'],
        'store'    => ['table' => 'stores',    'name_field' => 'name'],
    ];

    /**
     * 获取概览统计
     *
     * @return array{today: array, this_week: array, this_month: array, by_type: array}
     */
    public function getOverview(string $scope_type, array $scope_ids): array
    {
        $now = Carbon::now();
        $today_start = $now->copy()->startOfDay()->toDateTimeString();
        $today_end = $now->copy()->endOfDay()->toDateTimeString();
        $week_start = $now->copy()->startOfWeek()->toDateTimeString();
        $month_start = $now->copy()->startOfMonth()->toDateTimeString();

        return [
            'today' => [
                'success' => $this->countAllSuccess($today_start, $today_end, $scope_type, $scope_ids),
                'failed'  => $this->countFailed($today_start, $today_end, $scope_type, $scope_ids),
            ],
            'this_week' => [
                'success' => $this->countAllSuccess($week_start, $today_end, $scope_type, $scope_ids),
                'failed'  => $this->countFailed($week_start, $today_end, $scope_type, $scope_ids),
            ],
            'this_month' => [
                'success' => $this->countAllSuccess($month_start, $today_end, $scope_type, $scope_ids),
                'failed'  => $this->countFailed($month_start, $today_end, $scope_type, $scope_ids),
            ],
            'by_type' => $this->getByTypeStats($month_start, $today_end, $scope_type, $scope_ids),
        ];
    }

    /**
     * 获取趋势数据
     *
     * @return array<array{date: string, success: int, failed: int}>
     */
    public function getTrend(
        string $from,
        string $to,
        string $group_by,
        ?string $data_type,
        string $scope_type,
        array $scope_ids,
    ): array {
        $trunc = $this->getDateTrunc($group_by);
        $from_dt = $from . ' 00:00:00';
        $to_dt = $to . ' 23:59:59';

        // 成功数
        $success_map = $this->getTrendSuccess($trunc, $from_dt, $to_dt, $data_type, $scope_type, $scope_ids);

        // 失败数
        $failed_map = $this->getTrendFailed($trunc, $from_dt, $to_dt, $data_type, $scope_type, $scope_ids);

        // 合并所有日期
        $all_dates = array_unique(array_merge(array_keys($success_map), array_keys($failed_map)));
        sort($all_dates);

        $result = [];
        foreach ($all_dates as $date) {
            $result[] = [
                'date'    => $date,
                'success' => $success_map[$date] ?? 0,
                'failed'  => $failed_map[$date] ?? 0,
            ];
        }

        return $result;
    }

    /**
     * 获取分组统计
     *
     * @return array<array{id: int, name: string, success: int, failed: int}>
     */
    public function getBreakdown(
        string $dimension,
        string $from,
        string $to,
        ?string $data_type,
        string $scope_type,
        array $scope_ids,
    ): array {
        $from_dt = $from . ' 00:00:00';
        $to_dt = $to . ' 23:59:59';
        $dim_col = $dimension . '_id';
        $dim_config = self::DIMENSION_TABLE_MAP[$dimension];

        // 成功数按维度分组
        $success_map = $this->getBreakdownSuccess($dim_col, $from_dt, $to_dt, $data_type, $scope_type, $scope_ids);

        // 失败数按维度分组
        $failed_map = $this->getBreakdownFailed($dim_col, $from_dt, $to_dt, $data_type, $scope_type, $scope_ids);

        // 合并所有维度 ID
        $all_ids = array_unique(array_merge(array_keys($success_map), array_keys($failed_map)));

        if (empty($all_ids)) {
            return [];
        }

        // 查询维度名称
        $placeholders = implode(',', array_fill(0, count($all_ids), '?'));
        $name_rows = Db::select(
            "SELECT id, {$dim_config['name_field']} as name FROM {$dim_config['table']} WHERE id IN ({$placeholders})",
            array_values($all_ids)
        );
        $name_map = [];
        foreach ($name_rows as $row) {
            $name_map[$row->id] = $row->name;
        }

        $result = [];
        foreach ($all_ids as $id) {
            $result[] = [
                'id'      => (int) $id,
                'name'    => $name_map[$id] ?? '',
                'success' => $success_map[$id] ?? 0,
                'failed'  => $failed_map[$id] ?? 0,
            ];
        }

        // 按 success 降序排列
        usort($result, fn($a, $b) => $b['success'] <=> $a['success']);

        return $result;
    }

    /**
     * 统计所有实体表的成功总数
     */
    private function countAllSuccess(string $from, string $to, string $scope_type, array $scope_ids): int
    {
        $total = 0;
        foreach (self::TYPE_TABLE_MAP as $table) {
            $total += $this->countSuccess($table, $from, $to, $scope_type, $scope_ids);
        }
        return $total;
    }

    /**
     * 统计单个实体表的成功数
     */
    private function countSuccess(string $table, string $from, string $to, string $scope_type, array $scope_ids): int
    {
        $bindings = [$from, $to];
        $scope_sql = $this->buildScopeCondition($scope_type, $scope_ids, $bindings);

        $sql = "SELECT COUNT(*) as cnt FROM {$table} WHERE created_at BETWEEN ? AND ?{$scope_sql}";
        $result = Db::select($sql, $bindings);

        return (int) ($result[0]->cnt ?? 0);
    }

    /**
     * 统计失败消息数
     */
    private function countFailed(
        string $from,
        string $to,
        string $scope_type,
        array $scope_ids,
        ?string $data_type = null,
    ): int {
        $bindings = [$from, $to];
        $scope_sql = $this->buildScopeCondition($scope_type, $scope_ids, $bindings);
        $type_sql = '';
        if ($data_type !== null) {
            $type_sql = ' AND data_type = ?';
            $bindings[] = $data_type;
        }

        $sql = "SELECT COUNT(*) as cnt FROM failed_messages WHERE failed_at BETWEEN ? AND ?{$scope_sql}{$type_sql}";
        $result = Db::select($sql, $bindings);

        return (int) ($result[0]->cnt ?? 0);
    }

    /**
     * 按 data_type 分别统计成功和失败数
     */
    private function getByTypeStats(string $from, string $to, string $scope_type, array $scope_ids): array
    {
        $stats = [];
        foreach (self::VALID_DATA_TYPES as $type) {
            $success = 0;
            if (isset(self::TYPE_TABLE_MAP[$type])) {
                $success = $this->countSuccess(self::TYPE_TABLE_MAP[$type], $from, $to, $scope_type, $scope_ids);
            }
            $failed = $this->countFailed($from, $to, $scope_type, $scope_ids, $type);
            $stats[] = [
                'data_type' => $type,
                'success'   => $success,
                'failed'    => $failed,
            ];
        }
        return $stats;
    }

    /**
     * 趋势查询：成功数按日期分组
     *
     * @return array<string, int> date => count
     */
    private function getTrendSuccess(
        string $trunc,
        string $from,
        string $to,
        ?string $data_type,
        string $scope_type,
        array $scope_ids,
    ): array {
        $tables = $data_type !== null && isset(self::TYPE_TABLE_MAP[$data_type])
            ? [$data_type => self::TYPE_TABLE_MAP[$data_type]]
            : self::TYPE_TABLE_MAP;

        // inventory 没有实体表，跳过
        if ($data_type === 'inventory') {
            return [];
        }

        $union_parts = [];
        $all_bindings = [];

        foreach ($tables as $table) {
            $bindings = [$from, $to];
            $scope_sql = $this->buildScopeCondition($scope_type, $scope_ids, $bindings);
            $union_parts[] = "SELECT {$trunc} as period FROM {$table} WHERE created_at BETWEEN ? AND ?{$scope_sql}";
            $all_bindings = array_merge($all_bindings, $bindings);
        }

        if (empty($union_parts)) {
            return [];
        }

        $union_sql = implode(' UNION ALL ', $union_parts);
        $sql = "SELECT period::date::text as date, COUNT(*) as cnt FROM ({$union_sql}) sub GROUP BY period ORDER BY period";
        $rows = Db::select($sql, $all_bindings);

        $map = [];
        foreach ($rows as $row) {
            $map[$row->date] = (int) $row->cnt;
        }
        return $map;
    }

    /**
     * 趋势查询：失败数按日期分组
     *
     * @return array<string, int> date => count
     */
    private function getTrendFailed(
        string $trunc,
        string $from,
        string $to,
        ?string $data_type,
        string $scope_type,
        array $scope_ids,
    ): array {
        $bindings = [$from, $to];
        $scope_sql = $this->buildScopeCondition($scope_type, $scope_ids, $bindings);
        $type_sql = '';
        if ($data_type !== null) {
            $type_sql = ' AND data_type = ?';
            $bindings[] = $data_type;
        }

        $trunc_field = str_replace('created_at', 'failed_at', $trunc);
        $sql = "SELECT {$trunc_field}::date::text as date, COUNT(*) as cnt FROM failed_messages WHERE failed_at BETWEEN ? AND ?{$scope_sql}{$type_sql} GROUP BY {$trunc_field} ORDER BY {$trunc_field}";
        $rows = Db::select($sql, $bindings);

        $map = [];
        foreach ($rows as $row) {
            $map[$row->date] = (int) $row->cnt;
        }
        return $map;
    }

    /**
     * 分组统计：成功数按维度分组
     *
     * @return array<int, int> dimension_id => count
     */
    private function getBreakdownSuccess(
        string $dim_col,
        string $from,
        string $to,
        ?string $data_type,
        string $scope_type,
        array $scope_ids,
    ): array {
        $tables = $data_type !== null && isset(self::TYPE_TABLE_MAP[$data_type])
            ? [$data_type => self::TYPE_TABLE_MAP[$data_type]]
            : self::TYPE_TABLE_MAP;

        if ($data_type === 'inventory') {
            return [];
        }

        $union_parts = [];
        $all_bindings = [];

        foreach ($tables as $table) {
            $bindings = [$from, $to];
            $scope_sql = $this->buildScopeCondition($scope_type, $scope_ids, $bindings);
            $union_parts[] = "SELECT {$dim_col} FROM {$table} WHERE created_at BETWEEN ? AND ?{$scope_sql}";
            $all_bindings = array_merge($all_bindings, $bindings);
        }

        if (empty($union_parts)) {
            return [];
        }

        $union_sql = implode(' UNION ALL ', $union_parts);
        $sql = "SELECT {$dim_col} as dim_id, COUNT(*) as cnt FROM ({$union_sql}) sub GROUP BY {$dim_col}";
        $rows = Db::select($sql, $all_bindings);

        $map = [];
        foreach ($rows as $row) {
            $map[(int) $row->dim_id] = (int) $row->cnt;
        }
        return $map;
    }

    /**
     * 分组统计：失败数按维度分组
     *
     * @return array<int, int> dimension_id => count
     */
    private function getBreakdownFailed(
        string $dim_col,
        string $from,
        string $to,
        ?string $data_type,
        string $scope_type,
        array $scope_ids,
    ): array {
        $bindings = [$from, $to];
        $scope_sql = $this->buildScopeCondition($scope_type, $scope_ids, $bindings);
        $type_sql = '';
        if ($data_type !== null) {
            $type_sql = ' AND data_type = ?';
            $bindings[] = $data_type;
        }

        $sql = "SELECT {$dim_col} as dim_id, COUNT(*) as cnt FROM failed_messages WHERE failed_at BETWEEN ? AND ?{$scope_sql}{$type_sql} GROUP BY {$dim_col}";
        $rows = Db::select($sql, $bindings);

        $map = [];
        foreach ($rows as $row) {
            if ($row->dim_id !== null) {
                $map[(int) $row->dim_id] = (int) $row->cnt;
            }
        }
        return $map;
    }

    /**
     * 生成 DataScope SQL 条件片段
     */
    private function buildScopeCondition(string $scope_type, array $scope_ids, array &$bindings): string
    {
        if ($scope_type === 'all' || empty($scope_ids)) {
            return '';
        }

        $placeholders = implode(',', array_fill(0, count($scope_ids), '?'));

        if ($scope_type === 'store') {
            $bindings = array_merge($bindings, $scope_ids);
            return " AND store_id IN ({$placeholders})";
        }

        if ($scope_type === 'platform') {
            $bindings = array_merge($bindings, $scope_ids);
            return " AND platform_id IN ({$placeholders})";
        }

        return '';
    }

    /**
     * 获取 DATE_TRUNC SQL 表达式
     */
    private function getDateTrunc(string $group_by): string
    {
        return match ($group_by) {
            'day'   => "DATE_TRUNC('day', created_at)",
            'week'  => "DATE_TRUNC('week', created_at)",
            'month' => "DATE_TRUNC('month', created_at)",
        };
    }
}
