/*-
 * ~~~~~~licensing~~~~~~
 * schema-impl
 * ==========
 * Copyright (C) 2020 - 2021 EmDev LLC
 * ==========
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.system.registry.schema.impl;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import ru.entaxy.esb.system.registry.schema.api.ResourceService;
import ru.entaxy.esb.system.registry.schema.api.entity.Resource;
import ru.entaxy.esb.system.registry.schema.api.entity.ResourceInfo;
import ru.entaxy.esb.system.registry.schema.exception.ResourceInfoNotFound;
import ru.entaxy.esb.system.registry.schema.exception.ResourceNotFound;

import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
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> 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> 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;
    }
}
