<?php

declare(strict_types=1);

/**
 * 技師「本週推薦」：每會員每週對同一技師 1 次、每週最多 N 位；定點／非定點各自週榜。
 *
 * - fate_masseur_weekly_recommends  推薦紀錄（week_start 為週一 Y-m-d）
 */

require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_masseur_rewards.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_listing.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_page_counter.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'fate_store_masseurs.php';

/** 側欄週推榜筆數（完整週榜頁見 weekly_rank_limit 設定）。 */
const FATE_MASSEUR_WEEKLY_SIDEBAR_LIMIT = 5;

function fate_masseur_weekly_sidebar_rank_limit(): int
{
    return FATE_MASSEUR_WEEKLY_SIDEBAR_LIMIT;
}

function fate_masseur_weekly_recommend_table_ddl(): string
{
    return <<<'SQL'
CREATE TABLE IF NOT EXISTS `fate_masseur_weekly_recommends` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `masseur_id` INT UNSIGNED NOT NULL,
  `member_id` INT UNSIGNED NOT NULL,
  `week_start` DATE NOT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_member_masseur_week` (`member_id`, `masseur_id`, `week_start`),
  KEY `idx_week_masseur` (`week_start`, `masseur_id`),
  KEY `idx_member_week` (`member_id`, `week_start`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL;
}

function fate_masseur_weekly_recommend_ensure_table(PDO $pdo): void
{
    static $done = false;
    if ($done) {
        return;
    }
    try {
        $pdo->exec(fate_masseur_weekly_recommend_table_ddl());
        $done = true;
    } catch (Throwable) {
        /* 略過 */
    }
}

/** 本週週一 Y-m-d。 */
function fate_masseur_weekly_recommend_week_date(?int $unixTs = null): string
{
    return fate_stats_week_start_monday($unixTs);
}

function fate_masseur_weekly_recommend_max_per_week(): int
{
    $s = fate_masseur_rewards_settings();

    return max(1, min(50, (int) $s['weekly_recommend_max_per_week']));
}

function fate_masseur_weekly_rank_limit(): int
{
    $s = fate_masseur_rewards_settings();

    return max(1, min(50, (int) $s['weekly_rank_limit']));
}

function fate_masseur_weekly_stats_range_label(string $weekStartYmd): string
{
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $weekStartYmd)) {
        return '';
    }
    try {
        $tz = new DateTimeZone(date_default_timezone_get());
        $mon = new DateTimeImmutable($weekStartYmd . ' 00:00:00', $tz);
        $sun = $mon->modify('+6 days');

        return $mon->format('Y/m/d') . ' — ' . $sun->format('Y/m/d');
    } catch (Throwable) {
        return $weekStartYmd;
    }
}

/** 將查詢週次正規化為週一 Y-m-d，且不晚於本週、不早於上限週數。 */
function fate_masseur_weekly_normalize_view_week(string $raw, ?string $currentWeek = null, int $maxHistoryWeeks = 104): string
{
    $currentWeek = $currentWeek ?? fate_masseur_weekly_recommend_week_date();
    $raw = trim($raw);
    if ($raw === '' || preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw) !== 1) {
        return $currentWeek;
    }
    $maxHistoryWeeks = max(4, min(104, $maxHistoryWeeks));
    try {
        $tz = new DateTimeZone(date_default_timezone_get());
        $dt = new DateTimeImmutable($raw . ' 00:00:00', $tz);
        $dow = (int) $dt->format('N');
        $monday = $dt->modify('-' . ($dow - 1) . ' days');
        $mon = $monday->format('Y-m-d');
        $cur = new DateTimeImmutable($currentWeek . ' 00:00:00', $tz);
        if ($monday > $cur) {
            return $currentWeek;
        }
        $oldest = $cur->modify('-' . ($maxHistoryWeeks - 1) . ' weeks');
        if ($monday < $oldest) {
            return $oldest->format('Y-m-d');
        }

        return $mon;
    } catch (Throwable) {
        return $currentWeek;
    }
}

/**
 * 可查詢的週次列表（有推薦紀錄的週 + 本週），新到舊。
 *
 * @return list<string> Y-m-d 週一
 */
function fate_masseur_weekly_available_weeks(PDO $pdo, int $limit = 52): array
{
    fate_masseur_weekly_recommend_ensure_table($pdo);
    $limit = max(1, min(104, $limit));
    $current = fate_masseur_weekly_recommend_week_date();
    $weeks = [];
    try {
        $st = $pdo->query(
            'SELECT DISTINCT `week_start` FROM `fate_masseur_weekly_recommends`
             ORDER BY `week_start` DESC
             LIMIT ' . $limit
        );
        foreach ($st->fetchAll(PDO::FETCH_COLUMN) as $w) {
            $w = (string) $w;
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $w)) {
                $weeks[] = $w;
            }
        }
    } catch (Throwable) {
        $weeks = [];
    }
    if (!in_array($current, $weeks, true)) {
        array_unshift($weeks, $current);
    }

    return $weeks;
}

/**
 * @return array{used:int,max:int,recommended_ids:list<int>}
 */
function fate_masseur_weekly_recommend_member_usage(PDO $pdo, int $memberId, ?string $weekStart = null): array
{
    fate_masseur_weekly_recommend_ensure_table($pdo);
    $max = fate_masseur_weekly_recommend_max_per_week();
    if ($memberId <= 0) {
        return ['used' => 0, 'max' => $max, 'recommended_ids' => []];
    }
    $week = $weekStart ?? fate_masseur_weekly_recommend_week_date();
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $week)) {
        return ['used' => 0, 'max' => $max, 'recommended_ids' => []];
    }
    try {
        $st = $pdo->prepare(
            'SELECT `masseur_id` FROM `fate_masseur_weekly_recommends`
             WHERE `member_id` = ? AND `week_start` = ?
             ORDER BY `created_at` ASC, `id` ASC'
        );
        $st->execute([$memberId, $week]);
        $ids = [];
        foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $ids[] = (int) ($row['masseur_id'] ?? 0);
        }

        return ['used' => count($ids), 'max' => $max, 'recommended_ids' => $ids];
    } catch (Throwable) {
        return ['used' => 0, 'max' => $max, 'recommended_ids' => []];
    }
}

/**
 * @param list<int> $masseurIds
 * @return array<int,int> masseur_id => count
 */
function fate_masseur_weekly_recommend_counts_map(PDO $pdo, array $masseurIds, ?string $weekStart = null): array
{
    fate_masseur_weekly_recommend_ensure_table($pdo);
    $week = $weekStart ?? fate_masseur_weekly_recommend_week_date();
    $ids = array_values(array_unique(array_filter(array_map('intval', $masseurIds), static fn (int $id): bool => $id > 0)));
    if ($ids === [] || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $week)) {
        return [];
    }
    $place = implode(',', array_fill(0, count($ids), '?'));
    try {
        $st = $pdo->prepare(
            "SELECT `masseur_id`, COUNT(*) AS `cnt`
             FROM `fate_masseur_weekly_recommends`
             WHERE `week_start` = ? AND `masseur_id` IN ($place)
             GROUP BY `masseur_id`"
        );
        $st->execute(array_merge([$week], $ids));
        $out = [];
        foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $row) {
            if (!is_array($row)) {
                continue;
            }
            $out[(int) ($row['masseur_id'] ?? 0)] = (int) ($row['cnt'] ?? 0);
        }

        return $out;
    } catch (Throwable) {
        return [];
    }
}

/**
 * @return array{ok:bool,error:?string,weekly_count:int,used:int,max:int}
 */
function fate_masseur_weekly_recommend(PDO $pdo, int $masseurId, int $memberId, string $username = ''): array
{
    fate_masseur_weekly_recommend_ensure_table($pdo);
    $max = fate_masseur_weekly_recommend_max_per_week();
    if (!fate_masseur_weekly_recommend_enabled()) {
        return ['ok' => false, 'error' => '本週推薦功能目前未開放。', 'weekly_count' => 0, 'used' => 0, 'max' => $max];
    }
    if ($masseurId <= 0 || $memberId <= 0) {
        return ['ok' => false, 'error' => '參數錯誤。', 'weekly_count' => 0, 'used' => 0, 'max' => $max];
    }

    $masseur = fate_masseur_get($pdo, $masseurId);
    if ($masseur === null || (int) ($masseur['is_hidden'] ?? 0) === 1) {
        return ['ok' => false, 'error' => '找不到技師。', 'weekly_count' => 0, 'used' => 0, 'max' => $max];
    }

    $week = fate_masseur_weekly_recommend_week_date();
    $usage = fate_masseur_weekly_recommend_member_usage($pdo, $memberId, $week);
    if (in_array($masseurId, $usage['recommended_ids'], true)) {
        $cnt = fate_masseur_weekly_recommend_counts_map($pdo, [$masseurId], $week)[$masseurId] ?? 0;

        return ['ok' => false, 'error' => '您本週已推薦過這位技師。', 'weekly_count' => $cnt, 'used' => $usage['used'], 'max' => $max];
    }
    if ($usage['used'] >= $max) {
        return ['ok' => false, 'error' => '本週推薦名額已用完（最多 ' . $max . ' 位）。', 'weekly_count' => 0, 'used' => $usage['used'], 'max' => $max];
    }

    try {
        $st = $pdo->prepare(
            'INSERT INTO `fate_masseur_weekly_recommends` (`masseur_id`, `member_id`, `week_start`) VALUES (?, ?, ?)'
        );
        $st->execute([$masseurId, $memberId, $week]);
    } catch (Throwable $e) {
        if (str_contains($e->getMessage(), 'Duplicate') || str_contains($e->getMessage(), 'uq_member_masseur_week')) {
            return ['ok' => false, 'error' => '您本週已推薦過這位技師。', 'weekly_count' => 0, 'used' => $usage['used'], 'max' => $max];
        }

        return ['ok' => false, 'error' => '推薦失敗：' . $e->getMessage(), 'weekly_count' => 0, 'used' => $usage['used'], 'max' => $max];
    }

    fate_masseur_reward_grant($pdo, $memberId, 'weekly_recommend', $masseurId, $username, '本週推薦技師 #' . $masseurId);

    $cntMap = fate_masseur_weekly_recommend_counts_map($pdo, [$masseurId], $week);
    $newUsage = fate_masseur_weekly_recommend_member_usage($pdo, $memberId, $week);

    return [
        'ok' => true,
        'error' => null,
        'weekly_count' => $cntMap[$masseurId] ?? 1,
        'used' => $newUsage['used'],
        'max' => $max,
    ];
}

/**
 * 本週（或指定週）技師週推榜；定點／非定點各自排行，可選縣市。
 *
 * @return list<array<string,mixed>>
 */
function fate_masseur_weekly_rank_fetch(
    PDO $pdo,
    string $weekStartYmd,
    string $listingKind = FATE_STORE_LISTING_FIXED,
    ?string $cityName = null,
    int $limit = 10
): array {
    fate_masseur_weekly_recommend_ensure_table($pdo);
    fate_masseur_ensure_tables($pdo);

    $limit = max(1, min(50, $limit));
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $weekStartYmd)) {
        return [];
    }

    $listingCond = fate_store_listing_sql_condition_aliased(
        fate_store_listing_normalize_kind($listingKind),
        'snap'
    );
    $params = [$weekStartYmd];
    $citySql = '';
    $city = $cityName !== null ? trim($cityName) : '';
    if ($city !== '') {
        $citySql = ' AND snap.`city_name` = ?';
        $params[] = $city;
    }

    try {
        $sql = 'SELECT m.`id` AS masseur_id, m.`name` AS masseur_name, m.`gender`, m.`photo_rel`,
                m.`spa_store_id`, t.`week_recommends`,
                snap.`store_name`, snap.`city_name`, snap.`district_name`, snap.`payload_json`
                FROM (
                    SELECT wr.`masseur_id`, COUNT(*) AS week_recommends
                    FROM `fate_masseur_weekly_recommends` wr
                    WHERE wr.`week_start` = ?
                    GROUP BY wr.`masseur_id`
                ) t
                INNER JOIN `fate_store_masseurs` m ON m.`id` = t.`masseur_id` AND m.`is_hidden` = 0
                INNER JOIN `spa_store_snapshots` snap ON snap.`spa_store_id` = m.`spa_store_id`
                    AND snap.`is_approved` = 1
                    AND (' . $listingCond . ')' . $citySql . '
                ORDER BY t.`week_recommends` DESC, t.`masseur_id` ASC
                LIMIT ' . $limit;
        $st = $pdo->prepare($sql);
        $st->execute($params);
        $out = [];
        while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
            if (is_array($row)) {
                $out[] = $row;
            }
        }

        return $out;
    } catch (Throwable) {
        return [];
    }
}

/**
 * 會員本週（或指定週）推薦紀錄。
 *
 * @return list<array<string,mixed>>
 */
function fate_masseur_weekly_recommend_member_list(
    PDO $pdo,
    int $memberId,
    ?string $weekStart = null,
    int $limit = 50
): array {
    fate_masseur_weekly_recommend_ensure_table($pdo);
    if ($memberId <= 0) {
        return [];
    }
    $week = $weekStart ?? fate_masseur_weekly_recommend_week_date();
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $week)) {
        return [];
    }
    $limit = max(1, min(100, $limit));

    try {
        $st = $pdo->prepare(
            'SELECT wr.`id`, wr.`masseur_id`, wr.`week_start`, wr.`created_at`,
                    m.`name` AS masseur_name, m.`gender`, m.`photo_rel`, m.`spa_store_id`,
                    snap.`store_name`, snap.`city_name`, snap.`district_name`
             FROM `fate_masseur_weekly_recommends` wr
             INNER JOIN `fate_store_masseurs` m ON m.`id` = wr.`masseur_id`
             LEFT JOIN `spa_store_snapshots` snap ON snap.`spa_store_id` = m.`spa_store_id`
             WHERE wr.`member_id` = ? AND wr.`week_start` = ?
             ORDER BY wr.`created_at` ASC, wr.`id` ASC
             LIMIT ' . $limit
        );
        $st->execute([$memberId, $week]);
        $out = [];
        while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
            if (is_array($row)) {
                $out[] = $row;
            }
        }

        return $out;
    } catch (Throwable) {
        return [];
    }
}

/**
 * 後台／站方：查詢指定週全部推薦紀錄（分頁）。
 *
 * @return array{items:list<array<string,mixed>>,total:int}
 */
function fate_masseur_weekly_recommend_admin_list(
    PDO $pdo,
    string $weekStartYmd,
    int $page = 1,
    int $perPage = 50,
    int $masseurId = 0,
    int $memberId = 0
): array {
    fate_masseur_weekly_recommend_ensure_table($pdo);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $weekStartYmd)) {
        return ['items' => [], 'total' => 0];
    }
    $page = max(1, $page);
    $perPage = max(1, min(200, $perPage));
    $offset = ($page - 1) * $perPage;

    $where = ['wr.`week_start` = ?'];
    $params = [$weekStartYmd];
    if ($masseurId > 0) {
        $where[] = 'wr.`masseur_id` = ?';
        $params[] = $masseurId;
    }
    if ($memberId > 0) {
        $where[] = 'wr.`member_id` = ?';
        $params[] = $memberId;
    }
    $whereSql = implode(' AND ', $where);

    $memberQt = fate_masseur_member_table_qualified();
    $memberJoin = $memberQt !== null
        ? "LEFT JOIN $memberQt mem ON mem.`id` = wr.`member_id`"
        : '';
    $memberSelect = $memberQt !== null
        ? ', mem.`username`, mem.`display_name`'
        : ', NULL AS `username`, NULL AS `display_name`';

    try {
        $countSt = $pdo->prepare("SELECT COUNT(*) FROM `fate_masseur_weekly_recommends` wr WHERE $whereSql");
        $countSt->execute($params);
        $total = (int) $countSt->fetchColumn();

        $st = $pdo->prepare(
            "SELECT wr.`id`, wr.`masseur_id`, wr.`member_id`, wr.`week_start`, wr.`created_at`,
                    m.`name` AS masseur_name, m.`spa_store_id`,
                    snap.`store_name`$memberSelect
             FROM `fate_masseur_weekly_recommends` wr
             INNER JOIN `fate_store_masseurs` m ON m.`id` = wr.`masseur_id`
             LEFT JOIN `spa_store_snapshots` snap ON snap.`spa_store_id` = m.`spa_store_id`
             $memberJoin
             WHERE $whereSql
             ORDER BY wr.`created_at` DESC, wr.`id` DESC
             LIMIT $perPage OFFSET $offset"
        );
        $st->execute($params);
        $items = [];
        while ($row = $st->fetch(PDO::FETCH_ASSOC)) {
            if (is_array($row)) {
                $items[] = $row;
            }
        }

        return ['items' => $items, 'total' => $total];
    } catch (Throwable) {
        return ['items' => [], 'total' => 0];
    }
}

/** 週榜頁可用縣市（依快照）。 */
function fate_masseur_weekly_rank_cities(PDO $pdo, string $weekStartYmd): array
{
    fate_masseur_weekly_recommend_ensure_table($pdo);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $weekStartYmd)) {
        return [];
    }
    try {
        $st = $pdo->prepare(
            'SELECT DISTINCT snap.`city_name`
             FROM `fate_masseur_weekly_recommends` wr
             INNER JOIN `fate_store_masseurs` m ON m.`id` = wr.`masseur_id`
             INNER JOIN `spa_store_snapshots` snap ON snap.`spa_store_id` = m.`spa_store_id`
             WHERE wr.`week_start` = ? AND snap.`city_name` IS NOT NULL AND TRIM(snap.`city_name`) <> \'\'
             ORDER BY snap.`city_name` ASC'
        );
        $st->execute([$weekStartYmd]);
        $out = [];
        foreach ($st->fetchAll(PDO::FETCH_COLUMN) as $c) {
            $c = trim((string) $c);
            if ($c !== '') {
                $out[] = $c;
            }
        }

        return $out;
    } catch (Throwable) {
        return [];
    }
}
