/*-
 * ~~~~~~licensing~~~~~~
 * schema-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.platform.runtime.core.infrastructure.schema.impl;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import ru.entaxy.esb.platform.runtime.core.infrastructure.schema.api.ResourceService;
import ru.entaxy.esb.platform.runtime.core.infrastructure.schema.api.entity.Resource;
import ru.entaxy.esb.platform.runtime.core.infrastructure.schema.api.entity.ResourceInfo;
import ru.entaxy.esb.platform.runtime.core.infrastructure.schema.exception.ResourceInfoNotFound;
import ru.entaxy.esb.platform.runtime.core.infrastructure.schema.exception.ResourceNotFound;

import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Root;
import java.util.List;
import java.util.Optional;

public class ResourceServiceImpl implements ResourceService {

    private SessionFactory sessionFactory;

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

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

    @Override
    public long loadResource(Resource resource) {
        long id;
        try (Session s = getSession()) {
            s.getTransaction().begin();
            id = (long) s.save(resource);
            s.getTransaction().commit();
            s.close();
        }
        return id;
    }

    @Override
    public long reloadResource(Resource resource) {
        try (Session s = getSession()) {
            s.getTransaction().begin();
            s.saveOrUpdate(reloadingResource(resource));
            s.getTransaction().commit();
            s.close();
        }
        return resource.getId();
    }

    @Override
    public Optional<Resource> getResource(long id) {
        Optional<Resource> resource;
        try (Session s = getSession()) {
            s.getTransaction().begin();

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

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

    @Override
    public Optional<Resource> getResourceByNamespace(String namespace) {
        Optional<ResourceInfo> resourceInfo;
        Optional<Resource> resource;
        try (Session s = getSession()) {
            s.getTransaction().begin();

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

            if (resourceInfo.isEmpty()) {
                throw new ResourceInfoNotFound();
            }

            resource = getResource(resourceInfo.get().getResourceId());

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

    @Override
    public Optional<Resource> getResourceByName(String name) {
        Optional<ResourceInfo> resourceInfo;
        Optional<Resource> resource;
        try (Session s = getSession()) {
            s.getTransaction().begin();

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

            if (resourceInfo.isEmpty()) {
                throw new ResourceInfoNotFound();
            }

            resource = getResource(resourceInfo.get().getResourceId());
            s.getTransaction().commit();
            s.close();
        }
        return resource;
    }

    @Override
    public Optional<Resource> getResourceByFullName(String name) {
        Optional<ResourceInfo> resourceInfo;
        Optional<Resource> resource;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);

            Expression<String> concat = builder.concat(root.get("name"), root.get("path"));
            criteriaQuery.where(
                    builder.equal(concat, name)
            );
            resourceInfo = s.createQuery(criteriaQuery).uniqueResultOptional();

            if (resourceInfo.isEmpty()) {
                throw new ResourceInfoNotFound();
            }

            resource = getResource(resourceInfo.get().getResourceId());
            s.getTransaction().commit();
            s.close();
        }
        return resource;
    }

    @Override
    public Optional<Resource> getResourceByIdAndVersion(long id, String version) {
        Optional<ResourceInfo> resourceInfo;
        Optional<Resource> resource;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            criteriaQuery.where(
                    builder.and(
                            builder.equal(root.get("id"), id),
                            builder.equal(root.get("version"), version)
                    )
            );
            resourceInfo = s.createQuery(criteriaQuery).uniqueResultOptional();
            if (resourceInfo.isEmpty()) {
                throw new ResourceInfoNotFound();
            }

            resource = getResource(resourceInfo.get().getResourceId());

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

    @Override
    public void removeResource(long id) {
        try (Session session = getSession()) {
            session.getTransaction().begin();
            Optional<Resource> resource = getResource(id);
            if (resource.isEmpty()) {
                throw new ResourceNotFound("Resource not found with id {" + id + "}");
            }
            session.delete(Resource.class.getName(), resource.get());
            session.getTransaction().commit();
        }
    }

    @Override
    public long loadResourceInfo(ResourceInfo resourceInfo) {
        long id;
        try (Session s = getSession()) {
            s.getTransaction().begin();
            if (resourceInfo.getResourceId() == null || getResource(resourceInfo.getResourceId()).isEmpty()) {
                throw new ResourceNotFound("Resource not found id {" + resourceInfo.getResourceId() + "}");
            }
            id = (long) s.save(resourceInfo);
            s.getTransaction().commit();
            s.close();
        }
        return id;
    }

    @Override
    public long editResourceInfo(ResourceInfo resourceInfo) {
        try (Session s = getSession()) {
            s.getTransaction().begin();
            s.saveOrUpdate(editedResourceInfo(resourceInfo));
            s.getTransaction().commit();
            s.close();
        }
        return resourceInfo.getId();
    }

    @Override
    public Optional<ResourceInfo> getResourceInfo(long id) {
        Optional<ResourceInfo> resourceInfo;
        try (Session s = getSession()) {
            s.getTransaction().begin();

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

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

    @Override
    public Optional<ResourceInfo> getResourceInfoByNamespace(String namespace) {
        Optional<ResourceInfo> resourceInfo;
        try (Session s = getSession()) {
            s.getTransaction().begin();

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

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

    @Override
    public Optional<ResourceInfo> getResourceInfoByName(String name) {
        Optional<ResourceInfo> resourceInfo;
        try (Session s = getSession()) {
            s.getTransaction().begin();

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

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

    @Override
    public Optional<ResourceInfo> getResourceInfoByIdAndVersion(long id, String version) {
        Optional<ResourceInfo> resourceInfo;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            criteriaQuery.where(
                    builder.and(
                            builder.equal(root.get("id"), id),
                            builder.equal(root.get("version"), version)
                    )
            );
            resourceInfo = s.createQuery(criteriaQuery).uniqueResultOptional();

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

    @Override
    public void removeResourceInfo(long id) {
        try (Session session = getSession()) {
            session.getTransaction().begin();
            Optional<ResourceInfo> resourceInfo = getResourceInfo(id);
            if (resourceInfo.isEmpty()) {
                throw new ResourceInfoNotFound("Resource info not found with id {" + id + "}");
            }
            session.delete(ResourceInfo.class.getName(), resourceInfo.get());
            session.getTransaction().commit();
        }
    }

    @Override
    public List<ResourceInfo> getListResourceInfo() {
        List<ResourceInfo> list;
        try (Session session = getSession()) {
            session.getTransaction().begin();
            CriteriaQuery<ResourceInfo> criteriaQuery = session.getCriteriaBuilder().createQuery(ResourceInfo.class);
            criteriaQuery.from(ResourceInfo.class);
            list = session.createQuery(criteriaQuery).getResultList();
            session.getTransaction().commit();
        }
        return list;
    }

    @Override
    public List<ResourceInfo> getListResourceInfoByName(String name) {
        List<ResourceInfo> list;
        try (Session session = getSession()) {
            session.getTransaction().begin();

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            criteriaQuery.where(
                    builder.like(root.get("name"), "%" + name + "%")
            );
            list = session.createQuery(criteriaQuery).getResultList();
            session.getTransaction().commit();
        }
        return list;
    }

    @Override
    public List<ResourceInfo> getResourceInfoByFullName(String name) {
        List<ResourceInfo> list;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            Expression<String> concat = builder.concat(root.get("path"), root.get("name"));
            criteriaQuery.where(
                    builder.equal(concat, name)
            );
            list = s.createQuery(criteriaQuery).getResultList();

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

    @Override
    public List<ResourceInfo> getResourceInfoByPath(String path) {
        List<ResourceInfo> list;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            criteriaQuery.where(
                    builder.like(root.get("path"), "%" + path + "%")
            );
            list = s.createQuery(criteriaQuery).getResultList();

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

    @Override
    public List<ResourceInfo> getResourceInfoByFullPath(String path) {
        List<ResourceInfo> list;
        try (Session s = getSession()) {
            s.getTransaction().begin();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            criteriaQuery.where(
                    builder.equal(root.get("path"), path)
            );
            list = s.createQuery(criteriaQuery).getResultList();

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

    @Override
    public List<ResourceInfo> getListResourceInfoByNamespace(String namespace) {
        List<ResourceInfo> list;
        try (Session session = getSession()) {
            session.getTransaction().begin();

            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<ResourceInfo> criteriaQuery = builder.createQuery(ResourceInfo.class);
            Root<ResourceInfo> root = criteriaQuery.from(ResourceInfo.class);
            criteriaQuery.where(
                    builder.like(root.get("namespace"), "%" + namespace + "%")
            );
            list = session.createQuery(criteriaQuery).getResultList();
            session.getTransaction().commit();
        }
        return list;
    }

    private ResourceInfo editedResourceInfo(ResourceInfo newResourceInfo) {
        Optional<ResourceInfo> oldResource = getResourceInfo(newResourceInfo.getId());
        if (oldResource.isPresent()) {
            newResourceInfo.setCreatedBy(oldResource.get().getCreatedBy());
            newResourceInfo.setCreatedDate(oldResource.get().getCreatedDate());
        } else {
            throw new EntityNotFoundException();
        }
        return newResourceInfo;
    }

    private Resource reloadingResource(Resource newResource) {
        Optional<Resource> oldResource = getResource(newResource.getId());
        if (oldResource.isPresent()) {
            newResource.setCreatedBy(oldResource.get().getCreatedBy());
            newResource.setCreatedDate(oldResource.get().getCreatedDate());
            newResource.setResourceValue(oldResource.get().getResourceValue());
        } else {
            throw new EntityNotFoundException();
        }
        return newResource;
    }
}
