<?php
// Server-side validation rules (Section 16 of the blueprint). Client-side
// HTML5/JS is only for instant feedback — this file is the real gatekeeper,
// re-run on every request no matter what the browser already checked.

require_once __DIR__ . '/helpers.php';

/** Normalizes an amount string (comma or dot decimal separator) to "123.45", or null if invalid. */
function normalize_amount(string $raw): ?string
{
    $raw = str_replace(',', '.', trim($raw));

    if ($raw === '' || !preg_match('/^\d+(\.\d{1,2})?$/', $raw)) {
        return null;
    }

    return $raw;
}

function category_exists(int $id, string $type): bool
{
    $stmt = get_db()->prepare('SELECT 1 FROM categories WHERE id = :id AND type = :type AND is_active = 1');
    $stmt->execute(['id' => $id, 'type' => $type]);

    return (bool) $stmt->fetchColumn();
}

function payment_method_exists(int $id): bool
{
    $stmt = get_db()->prepare('SELECT 1 FROM payment_methods WHERE id = :id AND is_active = 1');
    $stmt->execute(['id' => $id]);

    return (bool) $stmt->fetchColumn();
}

/**
 * Validates raw form input for creating/editing an expense.
 * Returns ['errors' => [field => message], 'data' => [normalized values]].
 * `data` always has every key, even on failure, so the form can be redisplayed.
 */
function validate_expense_input(array $input, bool $requireDescription = true): array
{
    $errors = [];

    $amountRaw = trim((string) ($input['amount'] ?? ''));
    $amount = normalize_amount($amountRaw);
    if ($amount === null || (float) $amount <= 0 || (float) $amount > 99999999.99) {
        $errors['amount'] = 'Please enter an amount greater than 0.';
    }

    $date = trim((string) ($input['expense_date'] ?? ''));
    $dateObj = DateTime::createFromFormat('Y-m-d', $date);
    $minDate = new DateTime('2000-01-01');
    $maxDate = (new DateTime())->modify('+1 year');
    if (!$dateObj || $dateObj->format('Y-m-d') !== $date || $dateObj < $minDate || $dateObj > $maxDate) {
        $errors['expense_date'] = "That date doesn't look right — please check it.";
    }

    $categoryId = (int) ($input['category_id'] ?? 0);
    if ($categoryId <= 0 || !category_exists($categoryId, 'expense')) {
        $errors['category_id'] = 'Please pick a category.';
    }

    $paymentMethodRaw = trim((string) ($input['payment_method_id'] ?? ''));
    $paymentMethodId = null;
    if ($paymentMethodRaw !== '') {
        $paymentMethodId = (int) $paymentMethodRaw;
        if ($paymentMethodId <= 0 || !payment_method_exists($paymentMethodId)) {
            $errors['payment_method_id'] = 'Please pick a valid payment method.';
        }
    }

    $description = trim((string) ($input['description'] ?? ''));
    if ($requireDescription && $description === '') {
        $errors['description'] = 'Please add a short description.';
    } elseif (mb_strlen($description) > 255) {
        $errors['description'] = 'Descriptions can be up to 255 characters.';
    }

    return [
        'errors' => $errors,
        'data' => [
            'amount'            => $amount ?? $amountRaw,
            'expense_date'      => $date,
            'category_id'       => $categoryId,
            'payment_method_id' => $paymentMethodId,
            'description'       => $description !== '' ? $description : null,
        ],
    ];
}

function category_name_exists(string $name, string $type, ?int $excludeId = null): bool
{
    $sql = 'SELECT 1 FROM categories WHERE type = :type AND name = :name';
    $params = ['type' => $type, 'name' => $name];
    if ($excludeId !== null) {
        $sql .= ' AND id != :exclude_id';
        $params['exclude_id'] = $excludeId;
    }
    $stmt = get_db()->prepare($sql);
    $stmt->execute($params);

    return (bool) $stmt->fetchColumn();
}

function payment_method_name_exists(string $name, ?int $excludeId = null): bool
{
    $sql = 'SELECT 1 FROM payment_methods WHERE name = :name';
    $params = ['name' => $name];
    if ($excludeId !== null) {
        $sql .= ' AND id != :exclude_id';
        $params['exclude_id'] = $excludeId;
    }
    $stmt = get_db()->prepare($sql);
    $stmt->execute($params);

    return (bool) $stmt->fetchColumn();
}

/**
 * Validates a category add/edit (Section 16: name + color rules).
 * The `categories` table's name column is case-insensitive (utf8mb4_unicode_ci),
 * so a plain equality lookup already enforces "unique, case-insensitive".
 */
function validate_category_input(array $input, ?int $excludeId = null, string $type = 'expense'): array
{
    $errors = [];

    $name = trim((string) ($input['name'] ?? ''));
    if ($name === '' || mb_strlen($name) > 50) {
        $errors['name'] = 'Please enter a name (up to 50 characters).';
    } elseif (category_name_exists($name, $type, $excludeId)) {
        $errors['name'] = 'That name already exists.';
    }

    $color = trim((string) ($input['color'] ?? ''));
    if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
        $errors['color'] = 'Please choose a color.';
    }

    return ['errors' => $errors, 'data' => ['name' => $name, 'color' => $color]];
}

/** Validates a payment method add/edit (Section 16: name rules only — no color). */
function validate_payment_method_input(array $input, ?int $excludeId = null): array
{
    $errors = [];

    $name = trim((string) ($input['name'] ?? ''));
    if ($name === '' || mb_strlen($name) > 50) {
        $errors['name'] = 'Please enter a name (up to 50 characters).';
    } elseif (payment_method_name_exists($name, $excludeId)) {
        $errors['name'] = 'That name already exists.';
    }

    return ['errors' => $errors, 'data' => ['name' => $name]];
}

/**
 * Validates raw form input for creating/editing an income entry. Mirrors
 * validate_expense_input() minus payment_method_id, against income-type categories.
 */
function validate_income_input(array $input): array
{
    $errors = [];

    $amountRaw = trim((string) ($input['amount'] ?? ''));
    $amount = normalize_amount($amountRaw);
    if ($amount === null || (float) $amount <= 0 || (float) $amount > 99999999.99) {
        $errors['amount'] = 'Please enter an amount greater than 0.';
    }

    $date = trim((string) ($input['income_date'] ?? ''));
    $dateObj = DateTime::createFromFormat('Y-m-d', $date);
    $minDate = new DateTime('2000-01-01');
    $maxDate = (new DateTime())->modify('+1 year');
    if (!$dateObj || $dateObj->format('Y-m-d') !== $date || $dateObj < $minDate || $dateObj > $maxDate) {
        $errors['income_date'] = "That date doesn't look right — please check it.";
    }

    $categoryId = (int) ($input['category_id'] ?? 0);
    if ($categoryId <= 0 || !category_exists($categoryId, 'income')) {
        $errors['category_id'] = 'Please pick a category.';
    }

    $description = trim((string) ($input['description'] ?? ''));
    if (mb_strlen($description) > 255) {
        $errors['description'] = 'Descriptions can be up to 255 characters.';
    }

    return [
        'errors' => $errors,
        'data' => [
            'amount'      => $amount ?? $amountRaw,
            'income_date' => $date,
            'category_id' => $categoryId,
            'description' => $description !== '' ? $description : null,
        ],
    ];
}
