<?php

declare(strict_types=1);

const PROLUMI_LOGIN_WINDOW_SECONDS = 900;
const PROLUMI_LOGIN_PAIR_LIMIT = 5;
const PROLUMI_LOGIN_IP_LIMIT = 20;
const PROLUMI_SESSION_IDLE_SECONDS = 2700;
const PROLUMI_SESSION_MAX_SECONDS = 43200;
const PROLUMI_SESSION_REGENERATE_SECONDS = 900;

function apply_security_headers(): void
{
    if (headers_sent()) {
        return;
    }

    header('X-Content-Type-Options: nosniff');
    header('X-Frame-Options: DENY');
    header("Content-Security-Policy: frame-ancestors 'none'; base-uri 'self'; form-action 'self'");
    header('Referrer-Policy: no-referrer');
    header('Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()');
    header('Cache-Control: no-store, private');
    header('Pragma: no-cache');

    /*
     * Strict-Transport-Security jätetään tarkoituksella pois,
     * kunnes kaikki HTTPS- ja sertifikaattivirheet on ratkaistu.
     */
}

function client_ip_address(): string
{
    /*
     * Jaetulla hostingilla REMOTE_ADDR on turvallisin oletus.
     * X-Forwarded-For-arvoa ei luoteta ilman varmistettua välityspalvelinta.
     */
    $ip = trim((string) ($_SERVER['REMOTE_ADDR'] ?? ''));

    if ($ip === '' || filter_var($ip, FILTER_VALIDATE_IP) === false) {
        return 'unknown';
    }

    return mb_substr($ip, 0, 45);
}

function current_user_agent(): string
{
    $userAgent = trim((string) ($_SERVER['HTTP_USER_AGENT'] ?? 'unknown'));

    return mb_substr($userAgent, 0, 255);
}

function audit_log(
    PDO $pdo,
    string $action,
    ?string $entityType = null,
    ?int $entityId = null,
    ?string $description = null,
    array $metadata = [],
    ?int $userId = null
): void {
    try {
        $resolvedUserId = $userId;

        if ($resolvedUserId === null && isset($_SESSION['user_id'])) {
            $resolvedUserId = (int) $_SESSION['user_id'];
        }

        $metadataJson = null;

        if ($metadata !== []) {
            $metadataJson = json_encode(
                $metadata,
                JSON_UNESCAPED_UNICODE
                | JSON_UNESCAPED_SLASHES
                | JSON_INVALID_UTF8_SUBSTITUTE
            );
        }

        $resolvedOrganizationId = isset(
            $_SESSION['organization_id']
        )
            ? (int) $_SESSION['organization_id']
            : 0;

        /*
         * Yrityksen varsinainen audit-loki on aina yrityskohtainen.
         * Kirjautumista edeltävät anonyymit tapahtumat tallennetaan
         * tenant_security_events-tauluun login.php:ssä.
         */
        if ($resolvedOrganizationId <= 0) {
            error_log(
                'Audit-loki ohitettiin ilman yrityskontekstia: '
                . $action
            );

            return;
        }

        $statement = $pdo->prepare(
            'INSERT INTO audit_logs (
                organization_id,
                user_id,
                action,
                entity_type,
                entity_id,
                description,
                ip_address,
                user_agent,
                metadata_json
             ) VALUES (
                :organization_id,
                :user_id,
                :action,
                :entity_type,
                :entity_id,
                :description,
                :ip_address,
                :user_agent,
                :metadata_json
             )'
        );

        $statement->execute([
            'organization_id' => $resolvedOrganizationId,
            'user_id' => $resolvedUserId,
            'action' => mb_substr($action, 0, 100),
            'entity_type' => $entityType !== null
                ? mb_substr($entityType, 0, 60)
                : null,
            'entity_id' => $entityId,
            'description' => $description !== null
                ? mb_substr($description, 0, 500)
                : null,
            'ip_address' => client_ip_address(),
            'user_agent' => current_user_agent(),
            'metadata_json' => $metadataJson,
        ]);
    } catch (Throwable $exception) {
        error_log(
            'Audit-lokin kirjoitus epäonnistui: '
            . $exception->getMessage()
        );
    }
}

function normalize_login_username(string $username): string
{
    return mb_strtolower(trim($username));
}

function login_rate_limit_status(
    PDO $pdo,
    string $username,
    string $ipAddress
): array {
    $normalizedUsername = normalize_login_username($username);
    $windowStart = (new DateTimeImmutable())
        ->modify('-' . PROLUMI_LOGIN_WINDOW_SECONDS . ' seconds')
        ->format('Y-m-d H:i:s');

    $pairStatement = $pdo->prepare(
        'SELECT COUNT(*)
         FROM login_attempts
         WHERE username = :username
           AND ip_address = :ip_address
           AND was_successful = 0
           AND attempted_at >= :window_start'
    );

    $pairStatement->execute([
        'username' => $normalizedUsername,
        'ip_address' => $ipAddress,
        'window_start' => $windowStart,
    ]);

    $pairFailures = (int) $pairStatement->fetchColumn();

    $ipStatement = $pdo->prepare(
        'SELECT COUNT(*)
         FROM login_attempts
         WHERE ip_address = :ip_address
           AND was_successful = 0
           AND attempted_at >= :window_start'
    );

    $ipStatement->execute([
        'ip_address' => $ipAddress,
        'window_start' => $windowStart,
    ]);

    $ipFailures = (int) $ipStatement->fetchColumn();

    $isBlocked = $pairFailures >= PROLUMI_LOGIN_PAIR_LIMIT
        || $ipFailures >= PROLUMI_LOGIN_IP_LIMIT;

    return [
        'blocked' => $isBlocked,
        'pair_failures' => $pairFailures,
        'ip_failures' => $ipFailures,
        'retry_after_seconds' => $isBlocked
            ? PROLUMI_LOGIN_WINDOW_SECONDS
            : 0,
    ];
}

function record_login_attempt(
    PDO $pdo,
    string $username,
    string $ipAddress,
    bool $wasSuccessful
): void {
    $statement = $pdo->prepare(
        'INSERT INTO login_attempts (
            username,
            ip_address,
            was_successful
         ) VALUES (
            :username,
            :ip_address,
            :was_successful
         )'
    );

    $statement->execute([
        'username' => normalize_login_username($username),
        'ip_address' => $ipAddress,
        'was_successful' => $wasSuccessful ? 1 : 0,
    ]);
}

function clear_failed_login_attempts(
    PDO $pdo,
    string $username,
    string $ipAddress
): void {
    $statement = $pdo->prepare(
        'DELETE FROM login_attempts
         WHERE username = :username
           AND ip_address = :ip_address
           AND was_successful = 0'
    );

    $statement->execute([
        'username' => normalize_login_username($username),
        'ip_address' => $ipAddress,
    ]);
}

function cleanup_old_security_records(PDO $pdo): void
{
    try {
        if (random_int(1, 100) !== 1) {
            return;
        }

        $pdo->exec(
            "DELETE FROM login_attempts
             WHERE attempted_at < DATE_SUB(NOW(), INTERVAL 30 DAY)"
        );
    } catch (Throwable $exception) {
        error_log(
            'Turvallisuustietojen siivous epäonnistui: '
            . $exception->getMessage()
        );
    }
}

function initialize_authenticated_session(): void
{
    $now = time();

    $_SESSION['authenticated_at'] = $now;
    $_SESSION['last_activity_at'] = $now;
    $_SESSION['last_regenerated_at'] = $now;
}

function destroy_current_session(): void
{
    $_SESSION = [];

    if (ini_get('session.use_cookies')) {
        $cookieParameters = session_get_cookie_params();

        setcookie(
            session_name(),
            '',
            [
                'expires' => time() - 42000,
                'path' => $cookieParameters['path'],
                'domain' => $cookieParameters['domain'],
                'secure' => (bool) $cookieParameters['secure'],
                'httponly' => (bool) $cookieParameters['httponly'],
                'samesite' => 'Lax',
            ]
        );
    }

    if (session_status() === PHP_SESSION_ACTIVE) {
        session_destroy();
    }
}

function enforce_authenticated_session(): void
{
    if (!isset($_SESSION['user_id'])) {
        return;
    }

    $now = time();
    $authenticatedAt = (int) ($_SESSION['authenticated_at'] ?? $now);
    $lastActivityAt = (int) ($_SESSION['last_activity_at'] ?? $now);
    $lastRegeneratedAt = (int) ($_SESSION['last_regenerated_at'] ?? $now);

    $idleExpired = ($now - $lastActivityAt)
        > PROLUMI_SESSION_IDLE_SECONDS;

    $absoluteExpired = ($now - $authenticatedAt)
        > PROLUMI_SESSION_MAX_SECONDS;

    if ($idleExpired || $absoluteExpired) {
        destroy_current_session();
        redirect('/login.php?expired=1');
    }

    if (
        ($now - $lastRegeneratedAt)
        > PROLUMI_SESSION_REGENERATE_SECONDS
    ) {
        session_regenerate_id(true);
        $_SESSION['last_regenerated_at'] = $now;
    }

    $_SESSION['last_activity_at'] = $now;
}
