/*-
 * ~~~~~~licensing~~~~~~
 * basic-auth-impl
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.system.auth.basic.jpa.impl;

import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.system.auth.basic.jpa.api.BasicAuthService;
import ru.entaxy.esb.system.auth.basic.jpa.api.entity.BasicAuthAccount;
import ru.entaxy.esb.system.auth.basic.jpa.api.entity.field.EncryptionAlgorithm;
import ru.entaxy.esb.system.auth.basic.jpa.api.exception.BadPasswordException;
import ru.entaxy.esb.system.auth.basic.jpa.util.EncryptionHelper;

public class BasicAuthServiceImpl implements BasicAuthService {

    private static final Logger LOG = LoggerFactory.getLogger(BasicAuthServiceImpl.class);

    private SessionFactory sessionFactory;
    private String encryptionAlgorithm;
    private String encryptionSalt;
    // private PermissionService permissionService;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Session getSession() {
        return sessionFactory.openSession();
    }

    public void setEncryptionAlgorithm(String encryptionAlgorithm) {
        this.encryptionAlgorithm = encryptionAlgorithm;
    }

    public void setEncryptionSalt(String encryptionSalt) {
        this.encryptionSalt = encryptionSalt;
    }

/*
public PermissionService getPermissionService() {
    return permissionService;
}

public void setPermissionService(PermissionService permissionService) {
    this.permissionService = permissionService;
}
*/
    @Override
    public List<BasicAuthAccount> list() {
        List<BasicAuthAccount> list;
        try (Session s = getSession()) {
            s.getTransaction().begin();
            CriteriaQuery<BasicAuthAccount> cq = s.getCriteriaBuilder().createQuery(BasicAuthAccount.class);
            cq.from(BasicAuthAccount.class);
            list = s.createQuery(cq).getResultList();
            s.getTransaction().commit();
            s.close();
        }

        return list;
    }

    @Override
    public Optional<BasicAuthAccount> get(String login) {
        Optional<BasicAuthAccount> basicAuthAccount;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<BasicAuthAccount> criteriaQuery = builder.createQuery(BasicAuthAccount.class);
            Root<BasicAuthAccount> root = criteriaQuery.from(BasicAuthAccount.class);
            criteriaQuery.where(builder.equal(root.get("login"), login));
            basicAuthAccount = s.createQuery(criteriaQuery).uniqueResultOptional();

            s.getTransaction().commit();
            s.close();
        }
        return basicAuthAccount;
    }

    public Optional<BasicAuthAccount> getBySystem(String systemUuid) {
        Optional<BasicAuthAccount> basicAuthAccount;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<BasicAuthAccount> criteriaQuery = builder.createQuery(BasicAuthAccount.class);
            Root<BasicAuthAccount> root = criteriaQuery.from(BasicAuthAccount.class);
            criteriaQuery.where(builder.equal(root.get("systemUUID"), systemUuid));
            basicAuthAccount = s.createQuery(criteriaQuery).uniqueResultOptional();

            s.getTransaction().commit();
            s.close();
        }
        return basicAuthAccount;
    };

    public Optional<BasicAuthAccount> get(Session session, String login) {
        Optional<BasicAuthAccount> basicAuthAccount;
        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<BasicAuthAccount> criteriaQuery = builder.createQuery(BasicAuthAccount.class);
        Root<BasicAuthAccount> root = criteriaQuery.from(BasicAuthAccount.class);
        criteriaQuery.where(builder.equal(root.get("login"), login));
        basicAuthAccount = session.createQuery(criteriaQuery).uniqueResultOptional();
        return basicAuthAccount;
    }

    @Override
    public Optional<BasicAuthAccount> getByAuthorizationHeaderHash(String authorizationHeaderHash) {
        Optional<BasicAuthAccount> basicAuthAccount;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<BasicAuthAccount> criteriaQuery = builder.createQuery(BasicAuthAccount.class);
            Root<BasicAuthAccount> root = criteriaQuery.from(BasicAuthAccount.class);
            criteriaQuery.where(builder.equal(root.get("authorizationHeaderHash"), authorizationHeaderHash));
            basicAuthAccount = s.createQuery(criteriaQuery).uniqueResultOptional();

            s.getTransaction().commit();
            s.close();
        }
        return basicAuthAccount;
    }

    @Override
    public BasicAuthAccount save(BasicAuthAccount basicAuthAccount, boolean isExist) {
        try (Session s = getSession()) {
            s.getTransaction().begin();
            if (isExist) {
                s.update(basicAuthAccount);
            } else {
                s.persist(basicAuthAccount);
            }
            s.getTransaction().commit();
            s.close();
        }
        return basicAuthAccount;
    }

    @Override
    public BasicAuthAccount saveFull(String login, String passwordHash, String encryptionAlgorithm, String systemUUID,
            String authorizationHeaderHash, String description, String createdBy, String editedBy) {

        LOG.debug("Parameters " + login + " " + passwordHash + " " + encryptionAlgorithm
                + " " + systemUUID + " " + authorizationHeaderHash + " " + description + " " + createdBy + " "
                + editedBy);

        Optional<BasicAuthAccount> existingBasicAuthAccount = get(login);

        EncryptionAlgorithm encryptionAlgorithmObj = EncryptionAlgorithm.getByName(encryptionAlgorithm.toUpperCase());

        BasicAuthAccount basicAuthAccount = existingBasicAuthAccount.orElseGet(BasicAuthAccount::new);
        settingBasicAuthAccount(login, passwordHash, encryptionAlgorithmObj, systemUUID, authorizationHeaderHash,
                description, createdBy, editedBy, basicAuthAccount, existingBasicAuthAccount.isPresent());

        return save(basicAuthAccount, existingBasicAuthAccount.isPresent());
    }

    private BasicAuthAccount settingBasicAuthAccount(String login, String passwordHash,
            EncryptionAlgorithm encryptionAlgorithm,
            String systemUUID, String authorizationHeaderHash,
            String description, String createdBy, String editedBy,
            BasicAuthAccount basicAuthAccount, boolean isExist) {
        if (!isExist) {
            basicAuthAccount.setLogin(login);
            basicAuthAccount.setCreateDate(new Date());
            basicAuthAccount.setCreatedBy(createdBy);
        } else {
            basicAuthAccount.setEditDate(new Date());
            basicAuthAccount.setEditedBy(editedBy);
        }

        basicAuthAccount.setPasswordHash(passwordHash);
        basicAuthAccount.setEncryptionAlgorithm(encryptionAlgorithm);
        basicAuthAccount.setSystemUUID(systemUUID);
        basicAuthAccount.setAuthorizationHeaderHash(authorizationHeaderHash);
        basicAuthAccount.setDescription(description);
        return basicAuthAccount;
    }

    @Override
    public BasicAuthAccount saveCommon(String login, String passwordHash, String systemUUID, String createdBy,
            String editedBy)
            throws NoSuchAlgorithmException, BadPasswordException {
        if (null != passwordHash && !passwordHash.isEmpty()) {
            passwordHash = EncryptionHelper.encrypt(passwordHash, this.encryptionAlgorithm, this.encryptionSalt);
        } else {
            throw new BadPasswordException("Password not passed or empty!");
        }
        return saveFull(login, passwordHash, this.encryptionAlgorithm, systemUUID, "", "", createdBy, editedBy);
    }

    @Override
    public BasicAuthAccount saveShort(String login, String passwordHash, String createdBy, String editedBy)
            throws NoSuchAlgorithmException, BadPasswordException {
        return saveCommon(login, passwordHash, "", createdBy, editedBy);
    }

    @Override
    public void remove(String login) {
        try (Session s = getSession()) {
            s.getTransaction().begin();

            Optional<BasicAuthAccount> basicAuthAccount = get(s, login);
            basicAuthAccount.ifPresent((basicAuthAccount1) -> removeProc(s, basicAuthAccount1));
            s.getTransaction().commit();
            s.close();
        }
    }

    private void removeProc(Session session, BasicAuthAccount account) {
        // permissionService.removeAll(session, account.getId(), PermissionConstants.TYPE_ACCOUNT);
        // session.flush();
        session.delete(BasicAuthAccount.class.getName(), account);
    }

    @Override
    public boolean check(String login, String password) {
        if (login != null && password != null) {
            Optional<BasicAuthAccount> basicAuthAccountOpt = get(login);
            if (basicAuthAccountOpt.isPresent()) {
                BasicAuthAccount basicAuthAccount = basicAuthAccountOpt.get();
                try {
                    String passedPasswordHash = EncryptionHelper.encrypt(password,
                            basicAuthAccount.getEncryptionAlgorithm().getAlgorithmName(),
                            this.encryptionSalt);
                    if (passedPasswordHash.equals(basicAuthAccount.getPasswordHash())) {
                        return true;
                    }
                } catch (NoSuchAlgorithmException e) {
                    LOG.error("Encryption algorithm error", e);
                }
            }
        }
        return false;
    }


}
