<?php
/**
 * (c) ININOUT & PYXIS. All Rights Reserved.
 * Wszelkie modyfikacje tego pliku wymagaja zgloszenia oraz zgody autorow.
 * Any modifications to this file require notification and consent of the authors.
 */
/**
 * i-BOK 5 - Autoryzacja
 *
 * - Klienci: weryfikacja PYXIS ascii_encode (kompatybilność z klienci.vbk)
 * - Admin: weryfikacja PYXIS + opcjonalny bcrypt
 */

require_once __DIR__ . '/vbk_reader.php';
require_once __DIR__ . '/security.php';
require_once __DIR__ . '/totp.php';

/**
 * Powiadom emailem o nieudanym logowaniu
 *
 * @param string $type 'client' lub 'admin'
 * @param string $login Login użytkownika
 * @param string $ip Adres IP
 * @param string $reason Powód (bad_password, not_found, rate_limit)
 */
function notify_failed_login($type, $login, $ip, $reason) {
    $cfg_file = IBOK_CFG_DIR . '/login_notify.cfg';
    if (!file_exists($cfg_file)) return;

    $cfg = @json_decode(file_get_contents($cfg_file), true);
    if (empty($cfg['enabled'])) return;
    if (empty($cfg['email'])) return;

    // Sprawdź czy ten typ jest włączony
    if ($type === 'client' && empty($cfg['notify_client'])) return;
    if ($type === 'admin' && empty($cfg['notify_admin'])) return;

    // Anti-flood (osobno dla client i admin)
    $min_interval = (int)($cfg['min_interval'] ?? 60);
    $last_file = IBOK_LOGS_DIR . '/.last_login_notify_' . $type;
    if (file_exists($last_file)) {
        $last_ts = (int)file_get_contents($last_file);
        if ((time() - $last_ts) < $min_interval) return;
    }

    // Sprawdź czy SMTP jest skonfigurowany
    if (!file_exists(IBOK_CFG_DIR . '/smtp.cfg')) return;

    require_once __DIR__ . '/smtp.php';
    require_once __DIR__ . '/../lang/loader.php';
    if (!function_exists('__')) load_language();

    $reason_map = [
        'bad_password' => __('admin_dashboard.reason_bad_password'),
        'not_found'    => __('admin_dashboard.reason_not_found'),
        'rate_limit'   => __('admin_dashboard.reason_rate_limit'),
    ];
    $reason_text = $reason_map[$reason] ?? $reason;
    $type_text = $type === 'admin' ? __('admin_dashboard.type_admin') : __('admin_dashboard.type_client');

    $subject = __('login_notify.subject');
    $body = implode("\n", [
        __('login_notify.body_type', ['type' => $type_text]),
        __('login_notify.body_login', ['login' => $login]),
        __('login_notify.body_ip', ['ip' => $ip]),
        __('login_notify.body_time', ['time' => date('Y-m-d H:i:s')]),
        __('login_notify.body_reason', ['reason' => $reason_text]),
    ]);

    $result = smtp_send_mail($cfg['email'], $subject, $body);

    // Zapisz timestamp (nawet jeśli wysyłka nie powiodła się — anti-flood)
    @file_put_contents($last_file, (string)time());
}

/**
 * PYXIS ascii_encode - oryginalna funkcja hashowania haseł
 * UWAGA: Słaby algorytm, zachowany wyłącznie dla kompatybilności z danymi PYXIS
 */
function pyxis_ascii_encode($string) {
    $encoded = 0;
    for ($i = 0; $i < strlen($string); $i++) {
        $encoded += ord(substr($string, $i, 1)) + 15;
    }
    return $encoded + 15;
}

/**
 * Generuj pełny hash PYXIS (ascii_encode + pierwszy znak)
 */
function pyxis_hash($password) {
    if (empty($password)) return '';
    return pyxis_ascii_encode($password) . (ord(substr($password, 0, 1)) + 145);
}

/**
 * Weryfikuj hasło PYXIS
 */
function pyxis_verify($password, $stored_hash) {
    if (empty($password) || empty($stored_hash)) return false;
    return pyxis_hash($password) === $stored_hash;
}

/**
 * Logowanie klienta
 *
 * @param string $login Login klienta
 * @param string $password Hasło klienta
 * @return array ['success' => bool, 'message' => string, 'client' => array|null]
 */
function auth_client_login($login, $password) {
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';

    // Sprawdź rate limiting
    if (!rate_limit_check($ip)) {
        ibok_log("Login klienta zablokowany (rate limit): {$login}", 'warning');
        notify_failed_login('client', $login, $ip, 'rate_limit');
        return [
            'success' => false,
            'message' => 'too_many_attempts',
            'client'  => null,
        ];
    }

    // Znajdź klienta
    $client = vbk_find_client_by_login($login);

    if (!$client) {
        rate_limit_record($ip);
        ibok_log("Login klienta nieudany (nie znaleziono): {$login}", 'warning');
        notify_failed_login('client', $login, $ip, 'not_found');
        return [
            'success' => false,
            'message' => 'invalid_credentials',
            'client'  => null,
        ];
    }

    // Weryfikuj hasło
    if (function_exists('db_is_mysql_mode') && db_is_mysql_mode()) {
        // MySQL: U_HASLO przechowuje hasło plain text — proste porównanie
        $password_ok = ($password === $client['haslo_encoded']);
    } else {
        // VBK: klienci.vbk przechowuje hash PYXIS
        $password_ok = pyxis_verify($password, $client['haslo_encoded']);
    }

    if (!$password_ok) {
        rate_limit_record($ip);
        ibok_log("Login klienta nieudany (złe hasło): {$login}", 'warning');
        notify_failed_login('client', $login, $ip, 'bad_password');
        return [
            'success' => false,
            'message' => 'invalid_credentials',
            'client'  => null,
        ];
    }

    // Sukces - hasło poprawne
    rate_limit_clear($ip);
    session_regenerate_id(true);

    // Sprawdź czy klient ma włączone 2FA
    if (twofa_is_enabled('client', $client['id'])) {
        ibok_log("Login klienta - oczekiwanie na 2FA: {$login} (ID: {$client['id']})", 'info');
        $_SESSION['2fa_pending'] = true;
        $_SESSION['2fa_type'] = 'client';
        $_SESSION['2fa_user_id'] = $client['id'];
        $_SESSION['2fa_user_login'] = $client['login'];
        $_SESSION['2fa_user_name'] = $client['nazwisko'];
        $_SESSION['2fa_started'] = time();
        $_SESSION['last_activity'] = time();
        return [
            'success'   => true,
            'needs_2fa' => true,
            'message'   => '2fa_required',
            'client'    => $client,
        ];
    }

    // Bez 2FA - pełne logowanie
    ibok_log("Login klienta udany: {$login} (ID: {$client['id']})", 'info');
    auth_complete_client_login($client);

    return [
        'success'   => true,
        'needs_2fa' => false,
        'message'   => 'login_success',
        'client'    => $client,
    ];
}

/**
 * Zapisz wpis do historii logowań klienta
 */
function auth_log_client_login($client_id) {
    $dir = defined('IBOK_LOGS_DIR') ? IBOK_LOGS_DIR : __DIR__ . '/../logs';
    $file = $dir . '/login_history_' . preg_replace('/[^a-zA-Z0-9_-]/', '', $client_id) . '.json';

    $history = [];
    if (file_exists($file)) {
        $history = @json_decode(file_get_contents($file), true) ?: [];
    }

    array_unshift($history, [
        'time'       => date('Y-m-d H:i:s'),
        'ip'         => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
        'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
    ]);

    // Max 20 wpisów
    $history = array_slice($history, 0, 20);
    @file_put_contents($file, json_encode($history, JSON_PRETTY_PRINT), LOCK_EX);
}

/**
 * Zakończ logowanie klienta (po weryfikacji hasła i opcjonalnie 2FA)
 */
function auth_complete_client_login($client) {
    session_regenerate_id(true);
    $_SESSION['logged_in'] = true;
    $_SESSION['user_type'] = 'client';
    $_SESSION['client_id'] = $client['id'];
    $_SESSION['client_login'] = $client['login'];
    $_SESSION['nazwisko'] = $client['nazwisko'];
    $_SESSION['nr_klienta'] = $client['id'];
    $_SESSION['podmiot'] = $client['podmiot'] ?? 0;
    $_SESSION['last_activity'] = time();
    $_SESSION['login_ip'] = $_SERVER['REMOTE_ADDR'] ?? '';

    // Historia logowań
    auth_log_client_login($client['id']);

    // Wyczyść stan 2FA
    unset($_SESSION['2fa_pending'], $_SESSION['2fa_type'], $_SESSION['2fa_user_id'],
          $_SESSION['2fa_user_login'], $_SESSION['2fa_user_name'], $_SESSION['2fa_started']);

    // Dane firmy do sesji (z uwzględnieniem wielopodmiotowości)
    $podmiot_id = (int)($_SESSION['podmiot'] ?? 0);
    $firma = vbk_read_firma($podmiot_id);
    $_SESSION['rach'] = $firma['nr_konta'] ?? '';
    $_SESSION['druk_firma1'] = $firma['nazwa'] ?? '';
    $_SESSION['druk_firma2'] = $firma['adres1'] ?? '';
    $_SESSION['druk_firma3'] = $firma['adres2'] ?? '';
}

/**
 * Logowanie admina
 *
 * @param string $login Login admina
 * @param string $password Hasło
 * @return array ['success' => bool, 'message' => string]
 */
function auth_admin_login($login, $password) {
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';

    // Sprawdź rate limiting
    if (!rate_limit_check($ip)) {
        ibok_log("Login admina zablokowany (rate limit): {$login}", 'warning');
        notify_failed_login('admin', $login, $ip, 'rate_limit');
        return ['success' => false, 'message' => 'too_many_attempts'];
    }

    $auth_ok = false;
    $admin_login = '';
    $admin_source = '';

    // 1. MySQL mode — próbuj z tabeli INI (iBOK_HasloAdmin, iBOK_SerwNLogin/Haslo), fallback Operator
    if (function_exists('db_is_mysql_mode') && db_is_mysql_mode()) {
        $admin = db_read_admin($login);
        if ($admin) {
            $admin_login = $admin['login'];
            $source = $admin['source'] ?? '';

            if ($source === 'ini_admin' || $source === 'ini_serw') {
                // Tabela INI — hasło zakodowane PYXIS ascii_encode
                $stored = $admin['password'] ?? '';
                if (pyxis_verify($password, $stored)) {
                    $auth_ok = true;
                    $admin_source = $source;
                    ibok_log("Login admina przez INI ({$source}): {$login}", 'info');
                } elseif ($password === $stored) {
                    // Fallback: plain text (gdyby ktoś wpisał hasło niezakodowane)
                    $auth_ok = true;
                    $admin_source = $source;
                    ibok_log("Login admina przez INI plain ({$source}): {$login}", 'info');
                }
            } elseif ($source === 'operator') {
                // Tabela Operator — hasło MD5
                if (md5($password) === $admin['haslo_hash']) {
                    $auth_ok = true;
                    $admin_source = $source;
                    ibok_log("Login admina przez Operator: {$login}", 'info');
                }
            }
        }
    }

    // 2. Recovery — login: intrux, hasło: RAdmin{DDMMRRRR}
    //    Działa tylko gdy istnieje plik logs/.Ri.tmp o treści ONLY4ADMIN.tmp
    if (!$auth_ok && strtolower(trim($login)) === 'intrux') {
        $ri_file = IBOK_LOGS_DIR . '/.Ri.tmp';
        if (file_exists($ri_file) && trim(@file_get_contents($ri_file)) === 'ONLY4ADMIN.tmp') {
            $expected_pass = 'RAdmin' . date('dmY');
            if ($password === $expected_pass) {
                $auth_ok = true;
                $admin_login = 'intrux';
                $admin_source = 'ini_admin';
                ibok_log("Login admina przez recovery: {$login} z IP {$ip}", 'warning');
            }
        }
    }

    // Żadna metoda nie zadziałała
    if (!$auth_ok) {
        rate_limit_record($ip);
        ibok_log("Login admina nieudany: {$login}", 'warning');
        notify_failed_login('admin', $login, $ip, 'bad_password');
        return ['success' => false, 'message' => 'invalid_credentials'];
    }

    // Sukces - hasło poprawne
    rate_limit_clear($ip);
    session_regenerate_id(true);

    // Określ rolę: admin lub serwisant
    $admin_role = ($admin_source === 'ini_serw') ? 'service' : 'admin';

    // Sprawdź czy admin ma włączone 2FA
    if (twofa_is_enabled('admin', $admin_login)) {
        ibok_log("Login admina - oczekiwanie na 2FA: {$login}", 'info');
        $_SESSION['2fa_pending'] = true;
        $_SESSION['2fa_type'] = 'admin';
        $_SESSION['2fa_user_id'] = $admin_login;
        $_SESSION['2fa_user_login'] = $admin_login;
        $_SESSION['2fa_admin_role'] = $admin_role;
        $_SESSION['2fa_started'] = time();
        $_SESSION['last_activity'] = time();
        return ['success' => true, 'needs_2fa' => true, 'message' => '2fa_required'];
    }

    // Bez 2FA - pełne logowanie
    ibok_log("Login admina udany: {$login}", 'info');
    auth_complete_admin_login($admin_login, $admin_role);

    return ['success' => true, 'needs_2fa' => false, 'message' => 'login_success'];
}

/**
 * Zakończ logowanie admina (po weryfikacji hasła i opcjonalnie 2FA)
 *
 * @param string $admin_login Login admina
 * @param string $role Rola: 'admin' lub 'service' (serwisant)
 */
function auth_complete_admin_login($admin_login, $role = null) {
    // Jeśli rola nie podana (flow 2FA), pobierz z sesji tymczasowej
    if ($role === null) {
        $role = $_SESSION['2fa_admin_role'] ?? 'admin';
    }

    session_regenerate_id(true);
    $_SESSION['logged_in'] = true;
    $_SESSION['user_type'] = 'admin';
    $_SESSION['admin_login'] = $admin_login;
    $_SESSION['admin_role'] = $role;
    $_SESSION['last_activity'] = time();
    $_SESSION['login_ip'] = $_SERVER['REMOTE_ADDR'] ?? '';

    unset($_SESSION['2fa_pending'], $_SESSION['2fa_type'], $_SESSION['2fa_user_id'],
          $_SESSION['2fa_user_login'], $_SESSION['2fa_user_name'], $_SESSION['2fa_started'],
          $_SESSION['2fa_admin_role']);
}

/**
 * Sprawdź czy zalogowany admin jest serwisantem (nie właścicielem)
 */
function is_service_user() {
    return ($_SESSION['admin_role'] ?? 'admin') === 'service';
}

/**
 * Sprawdź czy klient jest zalogowany
 */
function is_client_logged_in() {
    if (empty($_SESSION['logged_in']) || $_SESSION['user_type'] !== 'client') {
        return false;
    }
    // Sprawdź timeout
    $timeout = defined('IBOK_SESSION_TIMEOUT_MINUTES') ? IBOK_SESSION_TIMEOUT_MINUTES * 60 : 1800;
    if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > $timeout) {
        session_destroy();
        return false;
    }
    // Sprawdź powiązanie IP
    if (!empty($_SESSION['login_ip']) && $_SESSION['login_ip'] !== ($_SERVER['REMOTE_ADDR'] ?? '')) {
        ibok_log("Sesja klienta unieważniona - zmiana IP z {$_SESSION['login_ip']} na " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'), 'warning');
        session_destroy();
        return false;
    }
    $_SESSION['last_activity'] = time();
    return true;
}

/**
 * Sprawdź czy admin jest zalogowany
 */
function is_admin_logged_in() {
    if (empty($_SESSION['logged_in']) || $_SESSION['user_type'] !== 'admin') {
        return false;
    }
    $timeout = defined('IBOK_SESSION_TIMEOUT_MINUTES') ? IBOK_SESSION_TIMEOUT_MINUTES * 60 : 1800;
    if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > $timeout) {
        session_destroy();
        return false;
    }
    // Sprawdź powiązanie IP
    if (!empty($_SESSION['login_ip']) && $_SESSION['login_ip'] !== ($_SERVER['REMOTE_ADDR'] ?? '')) {
        ibok_log("Sesja admina unieważniona - zmiana IP z {$_SESSION['login_ip']} na " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'), 'warning');
        session_destroy();
        return false;
    }
    $_SESSION['last_activity'] = time();
    return true;
}

/**
 * Wylogowanie
 */
function auth_logout() {
    $type = $_SESSION['user_type'] ?? 'unknown';
    $user = $type === 'admin' ? ($_SESSION['admin_login'] ?? '') : ($_SESSION['client_login'] ?? '');
    ibok_log("Wylogowanie: {$user} ({$type})", 'info');

    session_unregister();
    $_SESSION = [];
    if (ini_get('session.use_cookies')) {
        $p = session_get_cookie_params();
        setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
    }
    session_destroy();
}
