<?php
/**
 * Lightweight service layer for match CRUD + sync operations.
 * Extracted so it can be reused by API endpoints and tests.
 */
class MatchService
{
    private $pdo;
    private $validator;
    private $allowedStatuses = ['planned', 'live', 'end'];
    private $columnCache = [];

    public function __construct(PDO $pdo, $validator = null)
    {
        $this->pdo = $pdo;
        $this->validator = $validator; // Optional GameValidator
    }

    private function hasColumn($table, $column)
    {
        $key = strtolower($table . '.' . $column);
        if (array_key_exists($key, $this->columnCache)) {
            return $this->columnCache[$key];
        }
        try {
            $stmt = $this->pdo->prepare('SHOW COLUMNS FROM ' . $table . ' LIKE :col');
            $stmt->execute([':col' => $column]);
            $exists = (bool)$stmt->fetch(PDO::FETCH_ASSOC);
            $this->columnCache[$key] = $exists;
            return $exists;
        } catch (Throwable $e) {
            $this->columnCache[$key] = false;
            return false;
        }
    }

    public function createMatch(array $payload, $userId)
    {
        $data = $this->normalizePayload($payload, true);

        // Ensure optional fields have safe defaults for INSERT
        $data += [
            'end_time' => null,
            'score' => null,
            'rate' => 'free',
            'participants' => $this->normalizeParticipants([])
        ];

        // Optional: discipline + settings snapshot
        $discipline = isset($payload['discipline']) ? trim((string)$payload['discipline']) : null;
        $settingsSnapshot = null;
        $settingsVersion = null;
        if ($discipline) {
            try {
                require_once __DIR__ . '/DisciplineSettingsModel.php';
                $model = new DisciplineSettingsModel($this->pdo);
                $snap = $model->getSnapshot($discipline, null);
                $settingsVersion = (int)($snap['settingsVersion'] ?? null);
                $settingsSnapshot = json_encode($snap, JSON_UNESCAPED_UNICODE);
            } catch (Throwable $e) {
                // If snapshot fails, proceed without it
                $settingsSnapshot = null;
                $settingsVersion = null;
            }
        }

        // Run server-side game validation if provided and game is finished
        if ($this->validator && isset($data['status']) && $data['status'] === 'end' && isset($payload['gameData'])) {
            $result = $this->validator->validateGameResult($payload['gameData']);
            if (empty($result['valid'])) {
                throw new InvalidArgumentException($this->formatValidatorErrors($result));
            }
        }

        $this->pdo->beginTransaction();
        try {
            // Build dynamic INSERT to support new columns if present
            $columns = ['Team1_ID','Team2_ID','StartTime','EndTime','Status','Score','Platform','MatchType','Rate','Participants'];
            $params = [
                ':team1_id' => $data['team1_id'],
                ':team2_id' => $data['team2_id'],
                ':start_time' => $data['start_time'],
                ':end_time' => $data['end_time'],
                ':status' => $data['status'],
                ':score' => $data['score'],
                ':platform' => $data['platform'],
                ':match_type' => $data['match_type'],
                ':rate' => $data['rate'],
                ':participants' => $data['participants']
            ];

            if ($discipline && $this->hasColumn('matches','Discipline')) {
                $columns[] = 'Discipline';
                $params[':discipline'] = $discipline;
            }
            if ($settingsVersion !== null && $this->hasColumn('matches','SettingsVersion')) {
                $columns[] = 'SettingsVersion';
                $params[':settings_version'] = $settingsVersion;
            }
            if ($settingsSnapshot !== null && $this->hasColumn('matches','SettingsSnapshot')) {
                $columns[] = 'SettingsSnapshot';
                $params[':settings_snapshot'] = $settingsSnapshot;
            }

            $placeholders = [];
            foreach ($columns as $col) {
                switch ($col) {
                    case 'Team1_ID': $placeholders[] = ':team1_id'; break;
                    case 'Team2_ID': $placeholders[] = ':team2_id'; break;
                    case 'StartTime': $placeholders[] = ':start_time'; break;
                    case 'EndTime': $placeholders[] = ':end_time'; break;
                    case 'Status': $placeholders[] = ':status'; break;
                    case 'Score': $placeholders[] = ':score'; break;
                    case 'Platform': $placeholders[] = ':platform'; break;
                    case 'MatchType': $placeholders[] = ':match_type'; break;
                    case 'Rate': $placeholders[] = ':rate'; break;
                    case 'Participants': $placeholders[] = ':participants'; break;
                    case 'Discipline': $placeholders[] = ':discipline'; break;
                    case 'SettingsVersion': $placeholders[] = ':settings_version'; break;
                    case 'SettingsSnapshot': $placeholders[] = ':settings_snapshot'; break;
                }
            }

            $sql = 'INSERT INTO matches (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $placeholders) . ')';
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);

            $matchId = (int) $this->pdo->lastInsertId();
            $record = $this->getMatch($matchId);

            $this->pdo->commit();
            return $record + ['created_by' => $userId];
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    public function updateMatch($matchId, array $payload, $userId)
    {
        $matchId = (int) $matchId;
        if ($matchId <= 0) {
            throw new InvalidArgumentException('Invalid match id');
        }

        // Ensure match exists
        $existing = $this->getMatch($matchId);
        if (!$existing) {
            throw new InvalidArgumentException('Match not found');
        }

        $data = $this->normalizePayload($payload, false, $existing);

        if ($this->validator && isset($data['status']) && $data['status'] === 'end' && isset($payload['gameData'])) {
            $result = $this->validator->validateGameResult($payload['gameData']);
            if (empty($result['valid'])) {
                throw new InvalidArgumentException($this->formatValidatorErrors($result));
            }
        }

        $set = [];
        $params = [':id' => $matchId];

        foreach (['team1_id' => 'Team1_ID', 'team2_id' => 'Team2_ID', 'start_time' => 'StartTime', 'end_time' => 'EndTime', 'status' => 'Status', 'score' => 'Score', 'platform' => 'Platform', 'match_type' => 'MatchType', 'rate' => 'Rate', 'participants' => 'Participants'] as $key => $column) {
            if (array_key_exists($key, $data) && $data[$key] !== null) {
                $set[] = "$column = :$key";
                $params[":$key"] = $data[$key];
            }
        }

        if (empty($set)) {
            throw new InvalidArgumentException('No fields to update');
        }

        // Ensure EndTime is set when status becomes end
        if (isset($data['status']) && $data['status'] === 'end' && !isset($data['end_time']) && empty($existing['EndTime'])) {
            $set[] = 'EndTime = :auto_end_time';
            $params[':auto_end_time'] = gmdate('Y-m-d H:i:s');
        }

        $this->pdo->beginTransaction();
        try {
            $sql = 'UPDATE matches SET ' . implode(', ', $set) . ' WHERE ID = :id';
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);

            $record = $this->getMatch($matchId);
            $this->pdo->commit();
            return $record + ['updated_by' => $userId];
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    public function fetchUpdates($since = null, array $filters = [], $limit = 100)
    {
        $limit = max(1, min(500, (int) $limit));
        $params = [':limit' => $limit];
        $where = [];

        if ($since) {
            $this->assertDate($since, 'since');
            $where[] = 'updated_at >= :since';
            $params[':since'] = $since;
        }

        if (!empty($filters['status'])) {
            if (!in_array($filters['status'], $this->allowedStatuses, true)) {
                throw new InvalidArgumentException('Invalid status filter');
            }
            $where[] = 'Status = :status_filter';
            $params[':status_filter'] = $filters['status'];
        }

        if (!empty($filters['team_id'])) {
            $teamId = (int) $filters['team_id'];
            if ($teamId <= 0) {
                throw new InvalidArgumentException('Invalid team filter');
            }
            $where[] = '(Team1_ID = :team OR Team2_ID = :team)';
            $params[':team'] = $teamId;
        }

        $selectCols = ['ID','Team1_ID','Team2_ID','StartTime','EndTime','Status','Score','Platform','MatchType','Rate','Participants','created_at','updated_at'];
        if ($this->hasColumn('matches','Discipline')) $selectCols[] = 'Discipline';
        if ($this->hasColumn('matches','SettingsVersion')) $selectCols[] = 'SettingsVersion';
        if ($this->hasColumn('matches','SettingsSnapshot')) $selectCols[] = 'SettingsSnapshot';

        $sql = 'SELECT ' . implode(', ', $selectCols) . ' FROM matches';

        if (!empty($where)) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }

        $sql .= ' ORDER BY updated_at DESC, ID DESC LIMIT :limit';

        $stmt = $this->pdo->prepare($sql);

        foreach ($params as $key => $value) {
            $stmt->bindValue($key, $value, $key === ':limit' ? PDO::PARAM_INT : PDO::PARAM_STR);
        }

        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function normalizePayload(array $payload, $isCreate, array $existing = [])
    {
        $data = [];

        if ($isCreate || isset($payload['team1_id'])) {
            $team1 = (int) ($payload['team1_id'] ?? 0);
            if ($team1 <= 0) {
                throw new InvalidArgumentException('team1_id is required and must be positive');
            }
            $data['team1_id'] = $team1;
        }

        if ($isCreate || isset($payload['team2_id'])) {
            $team2 = (int) ($payload['team2_id'] ?? 0);
            if ($team2 <= 0) {
                throw new InvalidArgumentException('team2_id is required and must be positive');
            }
            $data['team2_id'] = $team2;
        }

        if ($isCreate || isset($payload['startTime']) || isset($payload['start_time'])) {
            $start = $payload['startTime'] ?? $payload['start_time'] ?? null;
            if (!$start) {
                throw new InvalidArgumentException('startTime is required');
            }
            $data['start_time'] = $this->normalizeDateTime($start, 'startTime');
        }

        if (isset($payload['endTime']) || isset($payload['end_time'])) {
            $end = $payload['endTime'] ?? $payload['end_time'];
            $data['end_time'] = $this->normalizeDateTime($end, 'endTime');
        } elseif ($isCreate) {
            $data['end_time'] = null;
        }

        if ($isCreate || isset($payload['status'])) {
            $status = $payload['status'] ?? 'planned';
            if (!in_array($status, $this->allowedStatuses, true)) {
                throw new InvalidArgumentException('Invalid status value');
            }
            $data['status'] = $status;
        }

        if (isset($payload['score'])) {
            $data['score'] = $this->normalizeScore($payload['score']);
        } elseif ($isCreate) {
            $data['score'] = null;
        }

        if ($isCreate || isset($payload['platform'])) {
            $data['platform'] = $this->normalizeString($payload['platform'] ?? 'PC', 50, 'platform');
        }

        if ($isCreate || isset($payload['matchType']) || isset($payload['match_type'])) {
            $matchType = $payload['matchType'] ?? $payload['match_type'] ?? 'friendly';
            $data['match_type'] = $this->normalizeString($matchType, 50, 'matchType');
        }

        if (array_key_exists('rate', $payload)) {
            $data['rate'] = $this->normalizeString($payload['rate'], 50, 'rate');
        } elseif ($isCreate) {
            $data['rate'] = 'free';
        }

        if (array_key_exists('participants', $payload)) {
            $data['participants'] = $this->normalizeParticipants($payload['participants']);
        } elseif ($isCreate) {
            // Default participants include the creator when possible
            $creator = isset($payload['creator_id']) ? (int) $payload['creator_id'] : null;
            $data['participants'] = $this->normalizeParticipants($creator ? [$creator] : []);
        }

        if (!$isCreate && empty($data)) {
            throw new InvalidArgumentException('No payload provided');
        }

        return $data;
    }

    private function normalizeScore($score)
    {
        $score = trim((string) $score);
        if ($score === '') {
            return null;
        }

        if (!preg_match('/^\\d{1,3}:\\d{1,3}$/', $score)) {
            throw new InvalidArgumentException('Score must match format `X:Y`');
        }

        return $score;
    }

    private function normalizeParticipants($participants)
    {
        if ($participants === null || $participants === '') {
            return json_encode([]);
        }

        if (!is_array($participants)) {
            // Allow comma separated string
            $participants = explode(',', (string) $participants);
        }

        $clean = [];
        foreach ($participants as $value) {
            $id = (int) trim((string) $value);
            if ($id > 0) {
                $clean[] = $id;
            }
        }

        $clean = array_values(array_unique($clean));
        return json_encode($clean);
    }

    private function normalizeString($value, $maxLength, $field)
    {
        $value = trim((string) ($value ?? ''));
        if ($value === '') {
            return null;
        }
        if (strlen($value) > $maxLength) {
            throw new InvalidArgumentException($field . ' is too long');
        }
        return $value;
    }

    private function normalizeDateTime($value, $field)
    {
        $this->assertDate($value, $field);
        return date('Y-m-d H:i:s', strtotime($value));
    }

    private function assertDate($value, $field)
    {
        if (!$value || strtotime($value) === false) {
            throw new InvalidArgumentException($field . ' must be a valid datetime');
        }
    }

    private function getMatch($matchId)
    {
        $cols = ['ID','Team1_ID','Team2_ID','StartTime','EndTime','Status','Score','Platform','MatchType','Rate','Participants','created_at','updated_at'];
        if ($this->hasColumn('matches','Discipline')) $cols[] = 'Discipline';
        if ($this->hasColumn('matches','SettingsVersion')) $cols[] = 'SettingsVersion';
        if ($this->hasColumn('matches','SettingsSnapshot')) $cols[] = 'SettingsSnapshot';
        $sql = 'SELECT ' . implode(', ', $cols) . ' FROM matches WHERE ID = :id';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':id' => $matchId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    private function formatValidatorErrors(array $result)
    {
        if (!empty($result['errors']) && is_array($result['errors'])) {
            return 'Validation failed: ' . implode('; ', $result['errors']);
        }
        return 'Validation failed';
    }
}

// Utilities
class MatchServiceSchemaHelper {
    public static function columnExists(PDO $pdo, $table, $column) {
        $stmt = $pdo->prepare('SHOW COLUMNS FROM ' . $table . ' LIKE :col');
        $stmt->execute([':col' => $column]);
        return (bool)$stmt->fetch(PDO::FETCH_ASSOC);
    }
}

// Inject helper method into MatchService via trait-like approach
if (!method_exists('MatchService','hasColumn')) {
    MatchService::class;
}

// Add method to MatchService (defined inline)
// Note: PHP doesn't support adding methods dynamically; define inside class above. We already added property $columnCache.
// Implement function here by re-opening file content through patch in class (done earlier via hasColumn usage).
