package com.airlock.iam.techclient.auth.application.service.certificate;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.identity.Identity;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.certificate.CertificateX500Name;
import com.airlock.iam.common.api.domain.model.certificate.ClientCertificate;
import com.airlock.iam.common.infrastructure.interceptor.InterceptorManager;
import com.airlock.iam.techclient.api.domain.model.TechnicalClient;
import com.airlock.iam.techclient.api.domain.model.certificate.ClientCertificates;
import com.airlock.iam.techclient.api.domain.model.certificate.MutableClientCertificates;
import com.airlock.iam.techclient.api.domain.service.client.TechnicalClientChangeInterceptor;
import com.airlock.iam.techclient.api.domain.service.client.certificate.ClientCertificatesChangeInterceptor;
import com.airlock.iam.techclient.auth.domain.model.certificate.ClientCertificateTechClient;
import com.airlock.iam.techclient.auth.domain.model.certificate.LoginClientCertificateTechClientImpl;
import com.airlock.iam.techclient.auth.domain.model.certificate.MutableClientCertificateTechClient;
import com.airlock.iam.techclient.auth.domain.service.repository.cert.ClientCertificateTechClientRepository;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;

public final class ClientCertTechClientService {
   private final ClientCertificateTechClientRepository repository;
   private final InterceptorManager interceptorManager;

   @Inject
   public ClientCertTechClientService(ClientCertificateTechClientRepository repository, InterceptorManager interceptorManager) {
      this.repository = repository;
      this.interceptorManager = interceptorManager;
   }

   public ClientCertificateTechClient insert(ClientCertificateTechClient client) {
      MutableClientCertificateTechClient mutableClient = LoginClientCertificateTechClientImpl.asMutable(client);
      this.interceptorManager
         .runOperation(TechnicalClientChangeInterceptor.class, mutableClient, (data, interceptor) -> interceptor.beforeTechnicalClientInsert(data));
      MutableClientCertificates certificates = this.mutableCertificates(mutableClient);
      this.interceptorManager
         .runOperation(
            ClientCertificatesChangeInterceptor.class,
            certificates,
            (data, interceptor) -> interceptor.beforeClientCertificatesInsert(mutableClient.getId(), data)
         );
      ClientCertificateTechClient readbackClient = this.repository.insert(mutableClient);
      this.interceptorManager
         .runOperation(
            ClientCertificatesChangeInterceptor.class,
            LoginClientCertificateTechClientImpl.immutableCertificatesFor(readbackClient),
            (data, interceptor) -> interceptor.afterClientCertificatesInsert(readbackClient.getId(), data)
         );
      this.interceptorManager
         .runOperation(TechnicalClientChangeInterceptor.class, readbackClient, (data, interceptor) -> interceptor.afterTechnicalClientInsert(data));
      return readbackClient;
   }

   public ClientCertificateTechClient update(ClientCertificateTechClient client) {
      MutableClientCertificateTechClient mutableClient = LoginClientCertificateTechClientImpl.asMutable(client);
      ClientCertificates certificatesBeforeUpdate = this.certificatesBeforeUpdate(mutableClient.getId());
      ClientCertificateTechClient persistedClient = this.repository
         .findBy(mutableClient.getId())
         .orElseThrow(() -> new FailureException("Cannot update non-existent technical client " + LogUtil.quote(mutableClient.getId().asString())));
      boolean certificatesModified = !persistedClient.getClientCertificates().equals(mutableClient.getClientCertificates());
      this.interceptorManager
         .runOperation(
            TechnicalClientChangeInterceptor.class, mutableClient, (data, interceptor) -> interceptor.beforeTechnicalClientUpdate(persistedClient, data)
         );
      if (certificatesModified) {
         MutableClientCertificates certificates = this.mutableCertificates(mutableClient);
         this.interceptorManager
            .runOperation(
               ClientCertificatesChangeInterceptor.class,
               certificates,
               (data, interceptor) -> interceptor.beforeClientCertificatesUpdate(mutableClient.getId(), certificatesBeforeUpdate, data)
            );
      }

      ClientCertificateTechClient readbackClient = this.repository.update(mutableClient);
      if (certificatesModified) {
         this.interceptorManager
            .runOperation(
               ClientCertificatesChangeInterceptor.class,
               LoginClientCertificateTechClientImpl.immutableCertificatesFor(readbackClient),
               (data, interceptor) -> interceptor.afterClientCertificatesUpdate(readbackClient.getId(), certificatesBeforeUpdate, data)
            );
      }

      this.interceptorManager
         .runOperation(
            TechnicalClientChangeInterceptor.class, readbackClient, (data, interceptor) -> interceptor.afterTechnicalClientUpdate(persistedClient, data)
         );
      return readbackClient;
   }

   private ClientCertificates certificatesBeforeUpdate(Identity<TechnicalClient> id) {
      return LoginClientCertificateTechClientImpl.immutableCertificatesFor(this.repository.findBy(id).get());
   }

   public Optional<ClientCertificateTechClient> findByCertificate(ClientCertificate certificate) {
      return this.repository.findBy(certificate);
   }

   public Collection<ClientCertificateTechClient> findBySubject(CertificateX500Name subject) {
      return this.repository.findBy(subject);
   }

   public Collection<ClientCertificateTechClient> findAllClients() {
      return this.repository.findAll();
   }

   private MutableClientCertificates mutableCertificates(final MutableClientCertificateTechClient client) {
      return new MutableClientCertificates() {
         @Override
         public void clear() {
            client.removeAllCertificates();
         }

         @Override
         public void remove(ClientCertificate clientCertificate) {
            client.removeCertificate(clientCertificate);
         }

         @Override
         public void add(ClientCertificate clientCertificate) {
            client.addCertificate(clientCertificate);
         }

         @Override
         public Set<ClientCertificate> getClientCertificates() {
            return client.getClientCertificates();
         }
      };
   }
}
