<?php

namespace WPSN\Modules\Redirects;

defined('ABSPATH') || exit;

/**
 * Stores skipped/unsupported/error redirect rows during migration.
 */
final class RedirectMigrationReportRepository
{
    private function tableName(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'wpsn_redirect_migration_items';
    }

    /**
     * @param array<string,mixed> $data
     * @param array<string,mixed> $meta
     */
    public function insert(string $jobId, string $source, string $reason, array $data, array $meta = []): int
    {
        global $wpdb;

        $jobId = sanitize_text_field($jobId);
        if ($jobId === '') {
            return 0;
        }

        $source = sanitize_key($source);
        $reason = sanitize_key($reason);
        if ($reason === '') {
            $reason = 'unknown';
        }

        $from = isset($data['from_url']) ? (string) $data['from_url'] : '';
        $to = array_key_exists('to_url', $data) ? (string) $data['to_url'] : '';
        $match = isset($data['match_type']) ? (string) $data['match_type'] : '';
        $code = isset($data['status_code']) ? (int) $data['status_code'] : 0;
        $enabled = isset($data['enabled']) ? (int) (bool) $data['enabled'] : 0;

        $raw = [];
        if (!empty($meta)) {
            $raw['meta'] = $meta;
        }

        // Keep a small snapshot of the source row/rule (for debugging) – trim long strings.
        $raw['data'] = $this->trimDeep($data, 400);

        $rawJson = wp_json_encode($raw);

        $table = $this->tableName();
        $ok = $wpdb->insert(
            $table,
            [
                'job_id' => $jobId,
                'source' => $source,
                'reason' => $reason,
                'from_url' => $this->trimStr($from, 255),
                'to_url' => $this->trimStr($to, 255),
                'match_type' => $this->trimStr($match, 20),
                'status_code' => $code,
                'enabled' => $enabled,
                'raw' => $rawJson,
                'created_at' => current_time('mysql'),
            ],
            ['%s','%s','%s','%s','%s','%s','%d','%d','%s','%s']
        );

        return $ok ? (int) $wpdb->insert_id : 0;
    }

    /**
     * @return array<string,int>
     */
    public function countByReason(string $jobId): array
    {
        global $wpdb;
        $jobId = sanitize_text_field($jobId);
        if ($jobId === '') {
            return [];
        }

        $table = $this->tableName();
        $sql = $wpdb->prepare(
            "SELECT reason, COUNT(*) AS c FROM {$table} WHERE job_id = %s GROUP BY reason ORDER BY c DESC",
            $jobId
        );

        $rows = $wpdb->get_results($sql, ARRAY_A);
        if (!is_array($rows)) {
            return [];
        }

        $out = [];
        foreach ($rows as $r) {
            if (!is_array($r)) {
                continue;
            }
            $reason = isset($r['reason']) ? (string) $r['reason'] : '';
            if ($reason === '') {
                continue;
            }
            $out[$reason] = (int) ($r['c'] ?? 0);
        }

        return $out;
    }

    /**
     * @return int
     */
    public function count(string $jobId, string $reason = ''): int
    {
        global $wpdb;
        $jobId = sanitize_text_field($jobId);
        $reason = sanitize_key($reason);
        if ($jobId === '') {
            return 0;
        }

        $table = $this->tableName();

        if ($reason !== '') {
            $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE job_id = %s AND reason = %s", $jobId, $reason);
        } else {
            $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE job_id = %s", $jobId);
        }

        return (int) $wpdb->get_var($sql);
    }

    /**
     * @return array<int, array<string,mixed>>
     */
    public function fetch(string $jobId, string $reason = '', int $offset = 0, int $limit = 50): array
    {
        global $wpdb;
        $jobId = sanitize_text_field($jobId);
        $reason = sanitize_key($reason);
        $offset = max(0, $offset);
        $limit = max(1, min(500, $limit));

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

        $table = $this->tableName();

        if ($reason !== '') {
            $sql = $wpdb->prepare(
                "SELECT * FROM {$table} WHERE job_id = %s AND reason = %s ORDER BY id ASC LIMIT %d OFFSET %d",
                $jobId,
                $reason,
                $limit,
                $offset
            );
        } else {
            $sql = $wpdb->prepare(
                "SELECT * FROM {$table} WHERE job_id = %s ORDER BY id ASC LIMIT %d OFFSET %d",
                $jobId,
                $limit,
                $offset
            );
        }

        $rows = $wpdb->get_results($sql, ARRAY_A);
        return is_array($rows) ? $rows : [];
    }

    public function deleteByJob(string $jobId): int
    {
        global $wpdb;
        $jobId = sanitize_text_field($jobId);
        if ($jobId === '') {
            return 0;
        }

        $table = $this->tableName();
        $ok = $wpdb->delete($table, ['job_id' => $jobId], ['%s']);
        return $ok !== false ? (int) $ok : 0;
    }

    /**
     * @param mixed $value
     * @return mixed
     */
    private function trimDeep($value, int $maxLen)
    {
        if (is_string($value)) {
            return $this->trimStr($value, $maxLen);
        }
        if (is_array($value)) {
            $out = [];
            $n = 0;
            foreach ($value as $k => $v) {
                $n++;
                if ($n > 60) {
                    $out['__truncated'] = true;
                    break;
                }
                $kk = is_string($k) ? $this->trimStr($k, 64) : $k;
                $out[$kk] = $this->trimDeep($v, $maxLen);
            }
            return $out;
        }
        return $value;
    }

    private function trimStr(string $s, int $maxLen): string
    {
        $s = (string) $s;
        if ($maxLen <= 0) {
            return '';
        }

        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            if (mb_strlen($s) > $maxLen) {
                return mb_substr($s, 0, $maxLen);
            }
            return $s;
        }

        if (strlen($s) > $maxLen) {
            return substr($s, 0, $maxLen);
        }
        return $s;
    }
}
