<?php
declare(strict_types=1);

if (basename(__FILE__) === basename($_SERVER['SCRIPT_FILENAME'] ?? '')) {
    http_response_code(403);
    exit('Forbidden');
}

function e(?string $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

function redirect(string $path): never
{
    if (ob_get_level()) {
        ob_end_clean();
    }
    header('Location: ' . APP_BASE . $path);
    exit;
}

function money(float $value): string
{
    return number_format($value, 2);
}

function format_bytes(int $bytes): string
{
    $units = ['B', 'KB', 'MB', 'GB'];
    $size = max(0, $bytes);
    $unit = 0;
    while ($size >= 1024 && $unit < count($units) - 1) {
        $size /= 1024;
        $unit++;
    }
    return number_format($size, $unit === 0 ? 0 : 2) . ' ' . $units[$unit];
}

function now(): string
{
    return date('Y-m-d H:i:s');
}

function flash(string $key, ?string $message = null): ?string
{
    if ($message !== null) {
        $_SESSION['flash'][$key] = $message;
        return null;
    }

    $message = $_SESSION['flash'][$key] ?? null;
    unset($_SESSION['flash'][$key]);
    return $message;
}

function post(string $key, mixed $default = ''): mixed
{
    return $_POST[$key] ?? $default;
}

function get_setting(string $key, ?string $default = ''): string
{
    $stmt = db()->prepare('SELECT setting_value FROM settings WHERE setting_key = ? LIMIT 1');
    $stmt->execute([$key]);
    return (string)($stmt->fetchColumn() ?: $default);
}

function update_setting(string $key, string $value): void
{
    $stmt = db()->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)');
    $stmt->execute([$key, $value]);
}

function public_path(string $path = ''): string
{
    $path = ltrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR);
    return dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'public' . ($path !== '' ? DIRECTORY_SEPARATOR . $path : '');
}

function public_url(string $path): string
{
    return APP_BASE . '/' . ltrim(str_replace('\\', '/', $path), '/');
}

function get_quotation_logo_path(): string
{
    $configured = trim(get_setting('quotation_logo_path', ''));
    if ($configured !== '') {
        $absolute = public_path($configured);
        if (is_file($absolute)) {
            return $absolute;
        }
    }

    $defaultPublicLogo = public_path('assets/images/fortunes-logo.png');
    if (is_file($defaultPublicLogo)) {
        return $defaultPublicLogo;
    }

    return '';
}

function get_quotation_logo_file_path(): string
{
    return get_quotation_logo_path();
}

function get_quotation_logo_url(bool $forPdf = false): string
{
    $path = get_quotation_logo_path();
    if ($forPdf) {
        return get_quotation_logo_data_uri();
    }

    if ($path === '' || !is_file($path)) {
        return '';
    }

    $publicRoot = realpath(public_path());
    $realPath = realpath($path);
    if ($publicRoot && $realPath && str_starts_with(str_replace('\\', '/', $realPath), str_replace('\\', '/', $publicRoot))) {
        $relative = ltrim(str_replace('\\', '/', substr($realPath, strlen($publicRoot))), '/');
        return public_url($relative);
    }

    return APP_BASE . '/assets/images/fortunes-logo.png';
}

function get_quotation_logo_data_uri(): string
{
    $path = get_quotation_logo_path();
    if ($path === '' || !is_file($path) || !is_readable($path)) {
        return '';
    }

    $mime = '';
    if (class_exists('finfo')) {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = (string)$finfo->file($path);
    }
    if (!in_array($mime, ['image/jpeg', 'image/png', 'image/webp'], true)) {
        $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
        $mime = match ($ext) {
            'jpg', 'jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'webp' => 'image/webp',
            default => '',
        };
    }
    if ($mime === '') {
        return '';
    }

    $bytes = file_get_contents($path);
    if ($bytes === false) {
        return '';
    }

    return 'data:' . $mime . ';base64,' . base64_encode($bytes);
}

function get_company_info(): array
{
    return [
        'name' => get_setting('company_name', 'Fortunes'),
        'address' => get_setting('company_address', 'Dhaka, Bangladesh'),
        'phone' => get_setting('company_phone', '+880 0000-000000'),
        'email' => get_setting('company_email', 'info@fortunes.local'),
    ];
}

function save_quotation_logo_upload(array $file): ?string
{
    if (empty($file['name'])) {
        return null;
    }

    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Logo upload failed.');
    }

    if ((int)($file['size'] ?? 0) > 2097152) {
        throw new RuntimeException('File size must be less than 2MB.');
    }

    $tmpPath = (string)($file['tmp_name'] ?? '');
    if ($tmpPath === '' || !is_uploaded_file($tmpPath)) {
        throw new RuntimeException('Logo upload failed.');
    }

    $originalExt = strtolower(pathinfo((string)$file['name'], PATHINFO_EXTENSION));
    if (!in_array($originalExt, ['jpg', 'jpeg', 'png', 'webp'], true)) {
        throw new RuntimeException('Invalid file type.');
    }

    $mime = '';
    if (class_exists('finfo')) {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = (string)$finfo->file($tmpPath);
    } elseif (function_exists('finfo_file')) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = $finfo ? (string)finfo_file($finfo, $tmpPath) : '';
        if ($finfo) {
            finfo_close($finfo);
        }
    }

    $allowed = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/webp' => 'webp',
    ];
    if (!isset($allowed[$mime])) {
        throw new RuntimeException('Invalid file type.');
    }

    $uploadRelativeDir = 'assets/uploads/quotation-logo';
    $uploadDir = public_path($uploadRelativeDir);
    if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true)) {
        throw new RuntimeException('Logo upload failed.');
    }
    if (!is_writable($uploadDir)) {
        throw new RuntimeException('Logo upload failed.');
    }

    $filename = 'quotation-logo-' . date('Ymd-His') . '-' . bin2hex(random_bytes(3)) . '.' . $allowed[$mime];
    $targetPath = $uploadDir . DIRECTORY_SEPARATOR . $filename;
    if (!move_uploaded_file($tmpPath, $targetPath)) {
        throw new RuntimeException('Logo upload failed.');
    }

    return $uploadRelativeDir . '/' . $filename;
}

function delete_old_quotation_logo(string $oldLogo, string $newLogo): void
{
    if ($oldLogo === '' || $oldLogo === $newLogo || $oldLogo === 'assets/images/fortunes-logo.png') {
        return;
    }
    $uploadRoot = realpath(public_path('assets/uploads/quotation-logo'));
    $oldReal = realpath(public_path($oldLogo));
    if ($uploadRoot && $oldReal && str_starts_with(str_replace('\\', '/', $oldReal), str_replace('\\', '/', $uploadRoot)) && is_file($oldReal)) {
        unlink($oldReal);
    }
}

function log_activity(string $action, string $description): void
{
    $userId = !empty($_SESSION['user']['id']) ? (int)$_SESSION['user']['id'] : null;
    $stmt = db()->prepare('INSERT INTO activity_logs (user_id, action, description, ip_address, user_agent, created_at) VALUES (?, ?, ?, ?, ?, ?)');
    $stmt->execute([
        $userId,
        $action,
        $description,
        $_SERVER['REMOTE_ADDR'] ?? '',
        substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
        now(),
    ]);
}

function validate_strong_password(string $password): array
{
    $errors = [];
    if (strlen($password) < 10) {
        $errors[] = 'Password must be at least 10 characters.';
    }
    if (!preg_match('/[A-Z]/', $password)) {
        $errors[] = 'Password must include an uppercase letter.';
    }
    if (!preg_match('/[a-z]/', $password)) {
        $errors[] = 'Password must include a lowercase letter.';
    }
    if (!preg_match('/\d/', $password)) {
        $errors[] = 'Password must include a number.';
    }
    if (!preg_match('/[^A-Za-z0-9]/', $password)) {
        $errors[] = 'Password must include a special character.';
    }
    return $errors;
}

function allowed_role(string $role): bool
{
    return in_array($role, ['admin', 'sales', 'engineer', 'viewer'], true);
}

function allowed_user_status(string $status): bool
{
    return in_array($status, ['active', 'inactive'], true);
}

function next_quotation_no(): string
{
    $year = (int)date('Y');
    $pdo  = db();

    // Use a dedicated counter table with FOR UPDATE to guarantee atomicity.
    // This prevents two simultaneous saves from generating the same number.
    try {
        $inTransaction = $pdo->inTransaction();
        if (!$inTransaction) {
            $pdo->beginTransaction();
        }

        // Lock this year's counter row exclusively so no other connection
        // can read or update it until we commit.
        $stmt = $pdo->prepare(
            'SELECT next_seq FROM quotation_counters WHERE counter_year = ? FOR UPDATE'
        );
        $stmt->execute([$year]);
        $row = $stmt->fetch();

        if ($row) {
            $seq = (int)$row['next_seq'];
            $pdo->prepare(
                'UPDATE quotation_counters SET next_seq = next_seq + 1 WHERE counter_year = ?'
            )->execute([$year]);
        } else {
            // First quotation of this year — seed the counter.
            // Check existing quotations first so we don't restart from 1.
            $existing = $pdo->prepare(
                "SELECT COALESCE(MAX(CAST(SUBSTRING(quotation_no, 10) AS UNSIGNED)), 0)
                 FROM quotations WHERE quotation_no LIKE ?"
            );
            $existing->execute(["FQS-$year-%"]);
            $seq = (int)$existing->fetchColumn() + 1;

            $pdo->prepare(
                'INSERT INTO quotation_counters (counter_year, next_seq) VALUES (?, ?)'
            )->execute([$year, $seq + 1]);
        }

        if (!$inTransaction) {
            $pdo->commit();
        }

        return sprintf('FQS-%04d-%04d', $year, $seq);

    } catch (Throwable $e) {
        // Roll back only if WE started the transaction
        if (!$inTransaction && $pdo->inTransaction()) {
            $pdo->rollBack();
        }
        // Last-resort fallback: timestamp-based number (still unique, just less pretty)
        return sprintf('FQS-%04d-T%s', $year, date('His'));
    }
}


function status_badge(string $status): string
{
    $map = [
        'draft' => 'secondary',
        'final' => 'primary',
        'sent' => 'info',
        'accepted' => 'success',
        'rejected' => 'danger',
    ];
    $class = $map[$status] ?? 'secondary';
    return '<span class="badge text-bg-' . $class . '">' . e(ucfirst($status)) . '</span>';
}
