<?php

declare(strict_types=1);

if (!defined('BASE_PATH')) {
    define('BASE_PATH', dirname(__DIR__));
}

function base_path(string $path = ''): string
{
    $root = rtrim(BASE_PATH, DIRECTORY_SEPARATOR);
    if ($path === '') {
        return $root;
    }

    return $root . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
}

function runtime_path(string $path = ''): string
{
    $runtimeRoot = trim((string) env('CAMPARTS_RUNTIME_PATH', ''));
    if ($runtimeRoot === '') {
        $runtimeRoot = base_path('var');
    }
    $runtimeRoot = rtrim($runtimeRoot, DIRECTORY_SEPARATOR);

    if ($path === '') {
        return $runtimeRoot;
    }

    return $runtimeRoot . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
}

function ensure_dir(string $path): void
{
    if (is_dir($path)) {
        return;
    }

    if (!mkdir($path, 0775, true) && !is_dir($path)) {
        throw new RuntimeException('Impossible de creer le dossier: ' . $path);
    }
}

function load_env(string $filePath): void
{
    if (!is_readable($filePath)) {
        return;
    }

    $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if ($lines === false) {
        return;
    }

    foreach ($lines as $line) {
        $trimmed = trim($line);
        if ($trimmed === '' || str_starts_with($trimmed, '#')) {
            continue;
        }

        $parts = explode('=', $trimmed, 2);
        if (count($parts) !== 2) {
            continue;
        }

        $key = trim($parts[0]);
        $value = trim($parts[1]);

        if ($key === '') {
            continue;
        }

        if ((str_starts_with($value, '"') && str_ends_with($value, '"')) || (str_starts_with($value, "'") && str_ends_with($value, "'"))) {
            $value = substr($value, 1, -1);
        }

        $_ENV[$key] = $value;
        $_SERVER[$key] = $value;
        putenv($key . '=' . $value);
    }
}

function env(string $key, mixed $default = null): mixed
{
    if (array_key_exists($key, $_ENV)) {
        return $_ENV[$key];
    }

    if (array_key_exists($key, $_SERVER)) {
        return $_SERVER[$key];
    }

    $value = getenv($key);
    if ($value === false) {
        return $default;
    }

    return $value;
}

function config(?string $key = null, mixed $default = null): mixed
{
    static $config = null;

    if ($config === null) {
        $config = [
            'app' => require base_path('config/app.php'),
            'database' => require base_path('config/database.php'),
        ];
    }

    if ($key === null || $key === '') {
        return $config;
    }

    $segments = explode('.', $key);
    $value = $config;

    foreach ($segments as $segment) {
        if (!is_array($value) || !array_key_exists($segment, $value)) {
            return $default;
        }

        $value = $value[$segment];
    }

    return $value;
}

function qi(string $identifier): string
{
    return '`' . str_replace('`', '``', $identifier) . '`';
}

function db(): PDO
{
    static $pdo = null;

    if ($pdo instanceof PDO) {
        return $pdo;
    }

    $dbConfig = config('database');
    if (!is_array($dbConfig)) {
        throw new RuntimeException('Configuration database invalide.');
    }

    $dsn = sprintf(
        'mysql:host=%s;port=%d;dbname=%s;charset=%s',
        (string) ($dbConfig['host'] ?? '127.0.0.1'),
        (int) ($dbConfig['port'] ?? 3306),
        (string) ($dbConfig['name'] ?? 'camparts'),
        (string) ($dbConfig['charset'] ?? 'utf8mb4')
    );

    $options = [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ];

    if (defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
        $collation = (string) ($dbConfig['collation'] ?? 'utf8mb4_unicode_ci');
        $charset = (string) ($dbConfig['charset'] ?? 'utf8mb4');
        $options[PDO::MYSQL_ATTR_INIT_COMMAND] = sprintf('SET NAMES %s COLLATE %s', $charset, $collation);
    }

    $pdo = new PDO(
        $dsn,
        (string) ($dbConfig['user'] ?? 'root'),
        (string) ($dbConfig['pass'] ?? ''),
        $options
    );

    return $pdo;
}

function table_exists(PDO $pdo, string $table): bool
{
    static $cache = [];

    if (array_key_exists($table, $cache) && $cache[$table] === true) {
        return $cache[$table];
    }

    $stmt = $pdo->prepare(
        'SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :table_name LIMIT 1'
    );
    $stmt->execute([':table_name' => $table]);

    $exists = $stmt->fetchColumn() !== false;
    $cache[$table] = $exists;

    return $exists;
}

function table_columns(PDO $pdo, string $table): array
{
    static $cache = [];

    if (array_key_exists($table, $cache)) {
        return $cache[$table];
    }

    if (!table_exists($pdo, $table)) {
        $cache[$table] = [];
        return [];
    }

    $stmt = $pdo->query('SHOW COLUMNS FROM ' . qi($table));
    $columns = [];
    foreach ($stmt->fetchAll() as $column) {
        if (isset($column['Field'])) {
            $columns[$column['Field']] = $column;
        }
    }

    $cache[$table] = $columns;

    return $columns;
}

function pick_column(PDO $pdo, string $table, array $candidates, bool $required = true): ?string
{
    $columns = table_columns($pdo, $table);

    foreach ($candidates as $candidate) {
        if (isset($columns[$candidate])) {
            return $candidate;
        }
    }

    if ($required) {
        throw new RuntimeException(sprintf('Colonne introuvable pour %s (%s)', $table, implode(', ', $candidates)));
    }

    return null;
}

function request_path(): string
{
    $uriPath = (string) (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/');
    $scriptDir = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? ''));
    $uriPath = '/' . ltrim($uriPath, '/');

    if ($scriptDir !== '' && $scriptDir !== '/' && str_starts_with($uriPath, $scriptDir . '/')) {
        $uriPath = substr($uriPath, strlen($scriptDir));
    } elseif ($scriptDir !== '' && $scriptDir !== '/' && $uriPath === $scriptDir) {
        $uriPath = '/';
    }

    return trim($uriPath, '/');
}

function normalize_api_path(string $path): string
{
    $path = trim($path, '/');
    if ($path === '') {
        return '';
    }

    $changed = true;
    while ($changed) {
        $changed = false;

        if ($path === 'index.php') {
            return '';
        }
        if (str_starts_with($path, 'index.php/')) {
            $path = substr($path, strlen('index.php/'));
            $changed = true;
            continue;
        }

        if ($path === 'api') {
            return '';
        }
        if (str_starts_with($path, 'api/')) {
            $path = substr($path, strlen('api/'));
            $changed = true;
            continue;
        }

        if (str_ends_with($path, '/api')) {
            return '';
        }

        $apiSegmentPosition = strpos($path, '/api/');
        if ($apiSegmentPosition !== false) {
            $path = substr($path, $apiSegmentPosition + strlen('/api/'));
            $changed = true;
            continue;
        }
    }

    return trim($path, '/');
}

function request_json(): array
{
    $raw = file_get_contents('php://input');
    if ($raw === false || trim($raw) === '') {
        return [];
    }

    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) {
        throw new InvalidArgumentException('JSON invalide.');
    }

    return $decoded;
}

function query_string(string $key, ?string $default = null): ?string
{
    $value = $_GET[$key] ?? $default;
    if ($value === null) {
        return null;
    }

    return trim((string) $value);
}

function query_int(string $key, int $default = 0, int $min = PHP_INT_MIN, int $max = PHP_INT_MAX): int
{
    $value = $_GET[$key] ?? null;
    if ($value === null || $value === '') {
        return $default;
    }

    if (!is_numeric($value)) {
        return $default;
    }

    $intValue = (int) $value;

    if ($intValue < $min) {
        return $min;
    }

    if ($intValue > $max) {
        return $max;
    }

    return $intValue;
}

function query_bool(string $key, bool $default = false): bool
{
    $value = $_GET[$key] ?? null;
    if ($value === null || $value === '') {
        return $default;
    }

    return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
}

function client_ip(): string
{
    $forwarded = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
    if ($forwarded !== '') {
        $parts = explode(',', $forwarded);
        $first = trim($parts[0]);
        if ($first !== '') {
            return $first;
        }
    }

    return (string) ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1');
}

function apply_cors_headers(): void
{
    $origin = (string) ($_SERVER['HTTP_ORIGIN'] ?? '');
    $allowedOrigins = config('app.cors_allowed_origins', []);
    if (!is_array($allowedOrigins)) {
        $allowedOrigins = [];
    }

    $allowCredentials = false;
    $originAllowed = false;

    if ($origin !== '') {
        $requestHostRaw = trim((string) ($_SERVER['HTTP_HOST'] ?? ''));
        $requestHost = strtolower(preg_replace('/:\d+$/', '', $requestHostRaw) ?? '');
        $originHost = strtolower((string) (parse_url($origin, PHP_URL_HOST) ?? ''));
        $originHost = preg_replace('/:\d+$/', '', $originHost) ?? '';

        if ($requestHost !== '' && $originHost !== '') {
            $originAllowed =
                $originHost === $requestHost
                || $originHost === ('www.' . $requestHost)
                || ('www.' . $originHost) === $requestHost;
        }
    }

    if (in_array('*', $allowedOrigins, true)) {
        if ($origin !== '') {
            // Credentials require an explicit origin (never '*').
            header('Access-Control-Allow-Origin: ' . $origin);
            header('Vary: Origin');
            $allowCredentials = true;
        } else {
            header('Access-Control-Allow-Origin: *');
        }
    } elseif ($origin !== '' && in_array($origin, $allowedOrigins, true)) {
        header('Access-Control-Allow-Origin: ' . $origin);
        header('Vary: Origin');
        $allowCredentials = true;
    } elseif ($origin !== '' && $originAllowed) {
        // Safe fallback for apex/www mixups when env origins are incomplete.
        header('Access-Control-Allow-Origin: ' . $origin);
        header('Vary: Origin');
        $allowCredentials = true;
    }

    if ($allowCredentials) {
        header('Access-Control-Allow-Credentials: true');
    }

    header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
    header('Access-Control-Max-Age: 600');
}

function json_response(array $payload, int $status = 200, array $headers = []): void
{
    http_response_code($status);

    foreach ($headers as $name => $value) {
        header((string) $name . ': ' . (string) $value);
    }

    header('Content-Type: application/json; charset=utf-8');

    echo json_encode(
        $payload,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE
    );
}

function error_response(string $message, int $status = 400, array $details = []): void
{
    json_response([
        'success' => false,
        'error' => $message,
        'details' => $details,
    ], $status);
}

function validate_reference(string $reference): bool
{
    return (bool) preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]{1,64}$/', $reference);
}

function api_base_path(): string
{
    $basePath = (string) config('app.api_base_path', '/api');
    $basePath = '/' . trim($basePath, '/');

    return $basePath === '/' ? '/api' : $basePath;
}

function normalize_decimal(float $value): float
{
    return (float) number_format($value, 2, '.', '');
}
