/*
    Предрейсовые осмотры - мобильное приложение
    Хук для управления авторизацией
*/

//---------------------
//Подключение библиотек
//---------------------

const React = require('react');
const { useAppLocalDbContext } = require('../components/layout/AppLocalDbProvider');
const { AUTH_SETTINGS_KEYS, DEFAULT_IDLE_TIMEOUT } = require('../config/authConfig');
const { ACTION_CODES, RESPONSE_STATES, ERROR_MESSAGES } = require('../config/authApi');
const { generateSecretKey, encryptData, decryptData } = require('../utils/secureStorage');
const { getPersistentDeviceId, isPersistentIdAvailable } = require('../utils/deviceId');

//-----------
//Тело модуля
//-----------

//Хук для управления авторизацией
function useAuth() {
    const { getSetting, setSetting, getAuthSession, setAuthSession, clearAuthSession, isDbReady } = useAppLocalDbContext();

    //Состояние авторизации
    const [session, setSession] = React.useState(null);
    const [isAuthenticated, setIsAuthenticated] = React.useState(false);
    const [isLoading, setIsLoading] = React.useState(false);
    const [isInitialized, setIsInitialized] = React.useState(false);
    const [error, setError] = React.useState(null);

    //Ссылка для контроллера отмены запросов
    const abortControllerRef = React.useRef(null);

    //Флаг: сессия восстановлена из хранилища при открытии приложения
    const sessionRestoredFromStorageRef = React.useRef(false);

    //Загрузка или получение постоянного идентификатора устройства
    const getDeviceId = React.useCallback(async () => {
        //Сначала проверяем сохранённый идентификатор
        let deviceId = await getSetting(AUTH_SETTINGS_KEYS.DEVICE_ID);

        if (deviceId) {
            //Проверяем доступность постоянного идентификатора
            const persistentAvailable = await isPersistentIdAvailable();

            if (persistentAvailable) {
                //Получаем постоянный идентификатор для сравнения
                const persistentId = await getPersistentDeviceId();

                //Если постоянный ID отличается - обновляем на постоянный
                if (persistentId && !deviceId.includes('-') && deviceId !== persistentId) {
                    deviceId = persistentId;
                    await setSetting(AUTH_SETTINGS_KEYS.DEVICE_ID, deviceId);
                }
            }

            return deviceId;
        }

        //Генерируем новый идентификатор
        deviceId = await getPersistentDeviceId();
        await setSetting(AUTH_SETTINGS_KEYS.DEVICE_ID, deviceId);

        return deviceId;
    }, [getSetting, setSetting]);

    //Загрузка или генерация уникального секретного ключа устройства
    const getSecretKey = React.useCallback(async () => {
        let secretKey = await getSetting(AUTH_SETTINGS_KEYS.DEVICE_SECRET_KEY);

        if (!secretKey) {
            secretKey = generateSecretKey();
            await setSetting(AUTH_SETTINGS_KEYS.DEVICE_SECRET_KEY, secretKey);
        }

        return secretKey;
    }, [getSetting, setSetting]);

    //Выполнение запроса к серверу
    const executeRequest = React.useCallback(async (serverUrl, payload) => {
        //Отменяем предыдущий запрос если есть
        if (abortControllerRef.current) {
            abortControllerRef.current.abort();
        }

        const abortController = new AbortController();
        abortControllerRef.current = abortController;

        try {
            const response = await fetch(serverUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(payload),
                signal: abortController.signal
            });

            if (!response.ok) {
                throw new Error(`${ERROR_MESSAGES.SERVER_ERROR}: ${response.status}`);
            }

            const data = await response.json();
            return data;
        } catch (fetchError) {
            if (fetchError.name === 'AbortError') {
                return null;
            }

            throw new Error(`${ERROR_MESSAGES.NETWORK_ERROR}: ${fetchError.message}`);
        } finally {
            abortControllerRef.current = null;
        }
    }, []);

    //Сохранение credentials после успешной аутентификации
    const saveCredentials = React.useCallback(
        async (login, password, deviceId) => {
            try {
                //Получаем секретный ключ устройства
                const secretKey = await getSecretKey();

                //Шифруем пароль используя секретный ключ и deviceId как соль
                const encryptedPassword = encryptData(password, secretKey, deviceId || '');

                await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, login);
                await setSetting(AUTH_SETTINGS_KEYS.SAVED_PASSWORD, encryptedPassword);
                await setSetting(AUTH_SETTINGS_KEYS.SAVE_PASSWORD_ENABLED, 'true');

                return true;
            } catch (saveError) {
                console.error('Ошибка сохранения credentials:', saveError);
                return false;
            }
        },
        [setSetting, getSecretKey]
    );

    //Загрузка сохранённых credentials
    const getSavedCredentials = React.useCallback(async () => {
        try {
            const savedLogin = await getSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN);

            //Если нет сохранённого логина - возвращаем null
            if (!savedLogin) {
                return null;
            }

            const savePasswordEnabled = (await getSetting(AUTH_SETTINGS_KEYS.SAVE_PASSWORD_ENABLED)) === 'true';

            //Если пароль не сохранялся - возвращаем только логин
            if (!savePasswordEnabled) {
                return {
                    login: savedLogin,
                    password: null,
                    savePasswordEnabled: false
                };
            }

            const savedPassword = await getSetting(AUTH_SETTINGS_KEYS.SAVED_PASSWORD);

            if (!savedPassword) {
                return {
                    login: savedLogin,
                    password: null,
                    savePasswordEnabled: false
                };
            }

            //Получаем deviceId и секретный ключ для расшифровки
            const deviceId = await getDeviceId();
            const secretKey = await getSecretKey();

            //Расшифровываем пароль
            const decryptedPassword = decryptData(savedPassword, secretKey, deviceId || '');

            return {
                login: savedLogin,
                password: decryptedPassword,
                savePasswordEnabled: true
            };
        } catch (loadError) {
            console.error('Ошибка загрузки credentials:', loadError);
            return null;
        }
    }, [getSetting, getDeviceId, getSecretKey]);

    //Очистка сохранённых credentials
    const clearSavedCredentials = React.useCallback(async () => {
        try {
            await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, '');
            await setSetting(AUTH_SETTINGS_KEYS.SAVED_PASSWORD, '');
            await setSetting(AUTH_SETTINGS_KEYS.SAVE_PASSWORD_ENABLED, 'false');
            return true;
        } catch (clearError) {
            console.error('Ошибка очистки credentials:', clearError);
            return false;
        }
    }, [setSetting]);

    //Вход в систему
    const login = React.useCallback(
        async ({ serverUrl, company, user, password, timeout, savePassword = false }) => {
            setIsLoading(true);
            setError(null);

            try {
                //Получаем идентификатор устройства
                const deviceId = await getDeviceId();

                //Формируем запрос
                const requestPayload = {
                    XREQUEST: {
                        XACTION: {
                            SCODE: ACTION_CODES.LOG_IN
                        },
                        XPAYLOAD: {
                            SUSER: user,
                            SPASSWORD: password,
                            STERMINAL: deviceId
                        }
                    }
                };

                //Добавляем организацию если указана
                if (company) {
                    requestPayload.XREQUEST.XPAYLOAD.SCOMPANY = company;
                }

                //Добавляем таймаут (используем значение по умолчанию если не указан)
                const timeoutValue = timeout && timeout > 0 ? timeout : DEFAULT_IDLE_TIMEOUT;
                requestPayload.XREQUEST.XPAYLOAD.NTIMEOUT = timeoutValue;

                //Выполняем запрос
                const response = await executeRequest(serverUrl, requestPayload);

                if (!response) {
                    return { success: false, error: ERROR_MESSAGES.NETWORK_ERROR };
                }

                //Проверяем ответ
                if (!response.XRESPONSE) {
                    return { success: false, error: ERROR_MESSAGES.INVALID_RESPONSE };
                }

                const { SSTATE, XPAYLOAD } = response.XRESPONSE;

                if (SSTATE !== RESPONSE_STATES.OK) {
                    return { success: false, error: XPAYLOAD?.SERROR || ERROR_MESSAGES.LOGIN_FAILED };
                }

                //Проверяем наличие списка организаций (требуется выбор)
                if (XPAYLOAD.XCOMPANIES && Array.isArray(XPAYLOAD.XCOMPANIES) && XPAYLOAD.XCOMPANIES.length > 0 && !XPAYLOAD.XCOMPANY) {
                    //Преобразуем массив организаций
                    const organizations = XPAYLOAD.XCOMPANIES.map(item => item.XCOMPANY || item);

                    return {
                        success: true,
                        needSelectCompany: true,
                        sessionId: XPAYLOAD.SSESSION,
                        user: XPAYLOAD.XUSER,
                        organizations,
                        serverUrl,
                        savePassword,
                        //Передаём данные для сохранения credentials после выбора организации
                        loginCredentials: {
                            login: user,
                            password,
                            deviceId
                        }
                    };
                }

                //Сохраняем сессию
                const sessionData = {
                    sessionId: XPAYLOAD.SSESSION,
                    serverUrl,
                    userRn: XPAYLOAD.XUSER?.NRN,
                    userCode: XPAYLOAD.XUSER?.SCODE,
                    userName: XPAYLOAD.XUSER?.SNAME,
                    companyRn: XPAYLOAD.XCOMPANY?.NRN,
                    companyName: XPAYLOAD.XCOMPANY?.SNAME,
                    savePassword
                };

                await setAuthSession(sessionData);
                await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, serverUrl);
                await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, user);

                if (savePassword) {
                    await saveCredentials(user, password, deviceId);
                }

                setSession(sessionData);
                setIsAuthenticated(true);
                sessionRestoredFromStorageRef.current = false;

                return {
                    success: true,
                    needSelectCompany: false,
                    session: sessionData
                };
            } catch (loginError) {
                const errorMessage = loginError.message || ERROR_MESSAGES.LOGIN_FAILED;
                setError(errorMessage);
                return { success: false, error: errorMessage };
            } finally {
                setIsLoading(false);
            }
        },
        [executeRequest, getDeviceId, setAuthSession, setSetting, saveCredentials]
    );

    //Выбор организации после входа (SET_COMPANY)
    const selectCompany = React.useCallback(
        async ({ serverUrl, sessionId, user, company, savePassword, loginCredentials }) => {
            setIsLoading(true);
            setError(null);

            try {
                //Формируем запрос SET_COMPANY
                const requestPayload = {
                    XREQUEST: {
                        XACTION: {
                            SCODE: ACTION_CODES.SET_COMPANY
                        },
                        XPAYLOAD: {
                            SSESSION: sessionId,
                            NCOMPANY: company.NRN,
                            SCOMPANY: company.SNAME
                        }
                    }
                };

                //Выполняем запрос
                const response = await executeRequest(serverUrl, requestPayload);

                if (!response) {
                    return { success: false, error: ERROR_MESSAGES.NETWORK_ERROR };
                }

                //Проверяем ответ
                if (!response.XRESPONSE) {
                    return { success: false, error: ERROR_MESSAGES.INVALID_RESPONSE };
                }

                const { SSTATE, XPAYLOAD } = response.XRESPONSE;

                if (SSTATE !== RESPONSE_STATES.OK) {
                    return { success: false, error: XPAYLOAD?.SERROR || 'Ошибка установки организации' };
                }

                //Сохраняем сессию
                const sessionData = {
                    sessionId,
                    serverUrl,
                    userRn: user?.NRN,
                    userCode: user?.SCODE,
                    userName: user?.SNAME,
                    companyRn: XPAYLOAD.XCOMPANY?.NRN || company.NRN,
                    companyName: XPAYLOAD.XCOMPANY?.SNAME || company.SNAME,
                    savePassword
                };

                await setAuthSession(sessionData);
                await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, serverUrl);
                const loginToSave = loginCredentials?.login || user?.SCODE || user?.SNAME || '';
                if (loginToSave) {
                    await setSetting(AUTH_SETTINGS_KEYS.SAVED_LOGIN, loginToSave);
                }

                if (savePassword && loginCredentials) {
                    await saveCredentials(loginCredentials.login, loginCredentials.password, loginCredentials.deviceId);
                }

                setSession(sessionData);
                setIsAuthenticated(true);
                sessionRestoredFromStorageRef.current = false;

                return {
                    success: true,
                    session: sessionData
                };
            } catch (selectError) {
                const errorMessage = selectError.message || 'Ошибка установки организации';
                setError(errorMessage);
                return { success: false, error: errorMessage };
            } finally {
                setIsLoading(false);
            }
        },
        [executeRequest, setAuthSession, setSetting, saveCredentials]
    );

    //Выход из системы
    const logout = React.useCallback(
        async (options = {}) => {
            const { skipServerRequest = false } = options;

            setIsLoading(true);
            setError(null);

            try {
                const currentSession = session || (await getAuthSession());

                //Запрос на сервер только при онлайн и если не отключено опцией
                if (!skipServerRequest && currentSession?.sessionId && currentSession?.serverUrl) {
                    const requestPayload = {
                        XREQUEST: {
                            XACTION: {
                                SCODE: ACTION_CODES.LOG_OUT
                            },
                            XPAYLOAD: {
                                SSESSION: currentSession.sessionId
                            }
                        }
                    };

                    try {
                        await executeRequest(currentSession.serverUrl, requestPayload);
                    } catch (logoutError) {
                        console.warn('Ошибка при выходе из системы:', logoutError);
                    }
                }

                await clearAuthSession();
                setSession(null);
                setIsAuthenticated(false);

                return { success: true };
            } catch (logoutError) {
                const errorMessage = logoutError.message || ERROR_MESSAGES.LOGOUT_FAILED;
                setError(errorMessage);
                return { success: false, error: errorMessage };
            } finally {
                setIsLoading(false);
            }
        },
        [session, getAuthSession, executeRequest, clearAuthSession]
    );

    //Проверка сессии
    const checkSession = React.useCallback(async () => {
        setIsLoading(true);
        setError(null);

        try {
            //Получаем сохраненную сессию
            const savedSession = await getAuthSession();

            if (!savedSession?.sessionId || !savedSession?.serverUrl) {
                setSession(null);
                setIsAuthenticated(false);
                return { success: false, isOffline: false, error: null };
            }

            //Формируем запрос
            const requestPayload = {
                XREQUEST: {
                    XACTION: {
                        SCODE: ACTION_CODES.CHECK_SESSION
                    },
                    XPAYLOAD: {
                        SSESSION: savedSession.sessionId
                    }
                }
            };

            //Выполняем запрос
            let response = null;
            try {
                response = await executeRequest(savedSession.serverUrl, requestPayload);
            } catch (requestError) {
                const isNetworkError = requestError.message && String(requestError.message).indexOf(ERROR_MESSAGES.NETWORK_ERROR) !== -1;
                if (isNetworkError) {
                    setSession(savedSession);
                    setIsAuthenticated(true);
                    return { success: true, isOffline: true, session: savedSession };
                }
                throw requestError;
            }

            if (!response) {
                setSession(savedSession);
                setIsAuthenticated(true);
                return { success: true, isOffline: true, session: savedSession };
            }

            //Проверяем ответ: если сервер ответил, но структура некорректна — не считаем оффлайн
            if (!response.XRESPONSE) {
                return { success: false, isOffline: false, error: ERROR_MESSAGES.INVALID_RESPONSE };
            }

            const { SSTATE, XPAYLOAD } = response.XRESPONSE;

            if (SSTATE !== RESPONSE_STATES.OK) {
                //Сессия недействительна - очищаем данные
                await clearAuthSession();
                setSession(null);
                setIsAuthenticated(false);
                return { success: false, isOffline: false, error: ERROR_MESSAGES.SESSION_EXPIRED };
            }

            //Проверяем необходимость выбора организации
            if (XPAYLOAD.XCOMPANIES && !XPAYLOAD.XCOMPANY) {
                const organizations = XPAYLOAD.XCOMPANIES.map(item => item.XCOMPANY || item);

                return {
                    success: true,
                    isOffline: false,
                    needSelectCompany: true,
                    sessionId: XPAYLOAD.SSESSION || savedSession.sessionId,
                    user: XPAYLOAD.XUSER,
                    organizations,
                    serverUrl: savedSession.serverUrl,
                    savePassword: savedSession.savePassword
                };
            }

            //Обновляем данные сессии
            const updatedSession = {
                sessionId: XPAYLOAD.SSESSION || savedSession.sessionId,
                serverUrl: savedSession.serverUrl,
                userRn: XPAYLOAD.XUSER?.NRN || savedSession.userRn,
                userCode: XPAYLOAD.XUSER?.SCODE || savedSession.userCode,
                userName: XPAYLOAD.XUSER?.SNAME || savedSession.userName,
                companyRn: XPAYLOAD.XCOMPANY?.NRN || savedSession.companyRn,
                companyName: XPAYLOAD.XCOMPANY?.SNAME || savedSession.companyName,
                savePassword: savedSession.savePassword
            };

            await setAuthSession(updatedSession);
            setSession(updatedSession);
            setIsAuthenticated(true);

            return { success: true, isOffline: false, session: updatedSession };
        } catch (checkError) {
            //Оффлайн только при ошибке сети; при прочих ошибках — не переключаем в оффлайн
            const isNetworkError = checkError.message && String(checkError.message).indexOf(ERROR_MESSAGES.NETWORK_ERROR) !== -1;
            const savedSession = await getAuthSession();

            if (isNetworkError && savedSession?.sessionId) {
                setSession(savedSession);
                setIsAuthenticated(true);
                return { success: true, isOffline: true, session: savedSession };
            }

            setSession(null);
            setIsAuthenticated(false);
            return { success: false, isOffline: false, error: checkError.message };
        } finally {
            setIsLoading(false);
        }
    }, [getAuthSession, executeRequest, clearAuthSession, setAuthSession]);

    //Инициализация при готовности БД
    React.useEffect(() => {
        if (!isDbReady || isInitialized) {
            return;
        }

        const initAuth = async () => {
            try {
                const savedSession = await getAuthSession();

                if (savedSession?.sessionId) {
                    setSession(savedSession);
                    setIsAuthenticated(true);
                    sessionRestoredFromStorageRef.current = true;
                }
            } catch (initError) {
                console.error('Ошибка инициализации авторизации:', initError);
            } finally {
                setIsInitialized(true);
            }
        };

        initAuth();
    }, [isDbReady, isInitialized, getAuthSession]);

    //Получить и сбросить флаг «сессия восстановлена при открытии» (для проверки соединения только при холодном старте)
    const getAndClearSessionRestoredFromStorage = React.useCallback(() => {
        const value = sessionRestoredFromStorageRef.current;
        sessionRestoredFromStorageRef.current = false;
        return value;
    }, []);

    //Отмена запросов при размонтировании
    React.useEffect(
        () => () => {
            if (abortControllerRef.current) {
                abortControllerRef.current.abort();
            }
        },
        []
    );

    return {
        //Состояние
        session,
        isAuthenticated,
        isLoading,
        isInitialized,
        error,

        //Методы
        login,
        logout,
        selectCompany,
        checkSession,
        getDeviceId,
        getSavedCredentials,
        clearSavedCredentials,
        getAuthSession,
        clearAuthSession,
        getAndClearSessionRestoredFromStorage,

        //Константы
        AUTH_SETTINGS_KEYS
    };
}

//----------------
//Интерфейс модуля
//----------------

module.exports = useAuth;
