package org.etsi.osl.cridge;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.etsi.osl.domain.model.kubernetes.KubernetesConfigMap;
import org.etsi.osl.domain.model.kubernetes.KubernetesSecret;
import org.etsi.osl.domain.model.kubernetes.KubernetesService;
import org.etsi.osl.tmf.rcm634.model.ResourceSpecification;
import org.etsi.osl.tmf.ri639.model.ResourceCreate;
import org.etsi.osl.tmf.ri639.model.ResourceStatusType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.yaml.snakeyaml.Yaml;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
import io.fabric8.kubernetes.client.utils.Serialization;

public class NamespaceWatcher {
  

  private static final Logger logger = LoggerFactory.getLogger( "org.etsi.osl.cridge" );
  
  private KubernetesClient kubernetesClient;

  private CatalogClient catalogClient; 
  
  private String nameSpacename;
  
  private Map<String, Object> headers;

  private ResourceSpecification kubernetesSecretResourceSpec = null;

  private ResourceSpecification kubernetesServiceResourceSpec = null;

  private ResourceSpecification kubernetesConfigMapResourceSpec = null;
  
  List< SharedIndexInformer<?>> informers = new ArrayList<>();
  
  private KubernetesClientResource kubernetesClientResource;
  
  
  public NamespaceWatcher(KubernetesClient kubernetesClient, CatalogClient catalogClient, String anameSpacename, Map<String, Object> aheaders, KubernetesClientResource akubernetesClientResource) {
    super();
    this.kubernetesClient = kubernetesClient;
    this.catalogClient = catalogClient;
    this.nameSpacename = anameSpacename;
    this.headers = aheaders;
    this.kubernetesClientResource = akubernetesClientResource;
    
    kubernetesSecretResourceSpec  = catalogClient.retrieveResourceSpecByNameCategoryVersion(
        KubernetesSecret.OSL_KUBSECRET_RSPEC_NAME, 
        KubernetesSecret.OSL_KUBSECRET_RSPEC_CATEGORY,
        KubernetesSecret.OSL_KUBSECRET_RSPEC_VERSION);
    
    kubernetesServiceResourceSpec  = catalogClient.retrieveResourceSpecByNameCategoryVersion(
        KubernetesService.OSL_KUBSERVICE_RSPEC_NAME, 
        KubernetesService.OSL_KUBSERVICE_RSPEC_CATEGORY,
        KubernetesService.OSL_KUBSERVICE_RSPEC_VERSION);
    

    kubernetesConfigMapResourceSpec  = catalogClient.retrieveResourceSpecByNameCategoryVersion(
        KubernetesConfigMap.OSL_KUBCMAP_RSPEC_NAME, 
        KubernetesConfigMap.OSL_KUBCMAP_RSPEC_CATEGORY,
        KubernetesConfigMap.OSL_KUBCMAP_RSPEC_VERSION);
  }
  
  public static NamespaceWatcher getNew(KubernetesClient kubernetesClient2, CatalogClient catalogClient2, String anameSpacename, Map<String, Object> aheaders, KubernetesClientResource akubernetesClientResource) {
    
    NamespaceWatcher aNamespaceWatchers = new NamespaceWatcher(kubernetesClient2, catalogClient2, anameSpacename, aheaders, akubernetesClientResource);
    
    SharedIndexInformer<Secret> rSecret = aNamespaceWatchers.createSecretWatchersFornamespace(anameSpacename, aheaders);
    aNamespaceWatchers.informers.add( rSecret );

    SharedIndexInformer<Service> rService = aNamespaceWatchers.createServiceWatcherssForNamespace(anameSpacename, aheaders);
    aNamespaceWatchers.informers.add( rService );

    SharedIndexInformer<ConfigMap> rCMap = aNamespaceWatchers.createConfigMapWatcherssForNamespace(anameSpacename, aheaders);
    aNamespaceWatchers.informers.add( rService );
    
    
    return aNamespaceWatchers;
    
  }

  

  
  
  /**
   * if the equivalent namespace for the service order is successfully created the create
   * related wathcers  for secrets, services, configmaps, pods, etc
   *     1) we need to create domainmodels for these
   *     2) we add them as support resources to our initial resource
   * @param nameSpacename
   * @param headers 
   * @return 
   */
  
  
  private SharedIndexInformer<Secret> createSecretWatchersFornamespace(String nameSpacename, Map<String, Object> headers) {
    //watcher for secrets
      SharedIndexInformer<Secret> shixInformer = this.kubernetesClient.secrets().inNamespace(nameSpacename).inform(new ResourceEventHandler<Secret>() {

            @Override
            public void onAdd(Secret obj) {
            logger.debug("Added Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                obj.getKind(), 
                obj.getMetadata().getName(),
                obj.getMetadata().getUid(),
                obj.getMetadata().getNamespace());
            
            headers.forEach(((hname, hval) ->{
              if (hval instanceof String s) {
                if ( hname.contains("org.etsi.osl")) {
                  logger.debug("Header: {} = {} ", hname, s );      
                  if ( obj.getMetadata() == null ) {
                    obj.setMetadata( new ObjectMeta());
                    obj.getMetadata().setLabels( new HashMap<String, String>());
                  }
                  obj.getMetadata().getLabels().put(hname, s);
                }
              }
            }));
            
            updateKubernetesSecretResourceInOSLCatalog( obj, false );     
            
              
            }

            @Override
            public void onUpdate(Secret oldObj, Secret newObj) {
              logger.debug("onUpdate Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                  newObj.getKind(), 
                  newObj.getMetadata().getName(),
                  newObj.getMetadata().getUid(),
                  newObj.getMetadata().getNamespace());
              if ( kubernetesClientResource.getNameSpacesTobeDeleted().get( newObj.getMetadata().getNamespace())==null ) {
                updateKubernetesSecretResourceInOSLCatalog( newObj, false );    
              } else {
                updateKubernetesSecretResourceInOSLCatalog( newObj, true );    
              }
              
            }

            @Override
            public void onDelete(Secret obj, boolean deletedFinalStateUnknown) {
              logger.debug("onDelete Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                  obj.getKind(), 
                  obj.getMetadata().getName(),
                  obj.getMetadata().getUid(),
                  obj.getMetadata().getNamespace());
              updateKubernetesSecretResourceInOSLCatalog( obj, true );    
              
            }

        
      }, 30 * 1000L); // resync period (set 0 for no resync);
    
      shixInformer.start();
      
      return shixInformer;
    
  }
  
  


  private void updateKubernetesSecretResourceInOSLCatalog(Secret resource, boolean toDelete) {
//    if (this.informers.size() == 0) {
//      logger.debug("Informers are 0. Will exit updateKubernetesSecretResourceInOSLCatalog");
//      return;
//    }
    ResourceCreate rs = this.KubernetesSecret2OpensliceResource( resource ).toResourceCreate();
    if (toDelete) {
      rs.setResourceStatus(ResourceStatusType.SUSPENDED);
    }
    catalogClient.createOrUpdateResourceByNameCategoryVersion( rs );  
    
  }
  
  
  public KubernetesSecret KubernetesSecret2OpensliceResource(Secret secret) {

    
    String baseCRD = String.format( "%s@%s@%s@%s",
        secret.getKind(),
        secret.getApiVersion(),
        kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster(),
        kubernetesClient.getMasterUrl().toExternalForm());

    KubernetesSecret kcrv = KubernetesSecret.builder().osl_KUBCRD_RSPEC_UUID( kubernetesSecretResourceSpec.getUuid() )
        .name( secret.getMetadata().getName()  
            + "@" 
            + secret.getMetadata().getNamespace()
            + "@" 
            +  kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster()
            + "@" + 
            kubernetesClient.getMasterUrl().toExternalForm()  )
        .version( secret.getApiVersion() )
        .currentContextCluster( kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster() )
        .clusterMasterURL( kubernetesClient.getMasterUrl().toString()  )
        .fullResourceName( "" )
        .namespace( Serialization.asJson( secret.getMetadata().getNamespace()) )
        .kind( secret.getKind() )
        .apiGroup( secret.getPlural() )
        .uID( secret.getMetadata().getUid() )
        .metadata(Serialization.asJson( secret.getMetadata())  ) //.metadata( gkr.getMetadata().toString() )        
        .description( 
            String.format( "A secret in namespace %s on %s", secret.getMetadata().getNamespace(),  baseCRD ))
        .build();


    secret.getMetadata().getLabels().forEach((pk, pv) -> {
      logger.debug("\t label: {} {} ", pk, pv);
      kcrv.getProperties().put( pk  , pv);

    });
    

    if (secret.getData()  != null)
      secret.getData().forEach((kPropName, vProVal) -> {
        logger.debug("propName={} propValue={} ", kPropName, vProVal );        
        kcrv.getData().put(kPropName, vProVal);
      });
    if (secret.getStringData()  != null)
      secret.getStringData().forEach((kPropName, vProVal) -> {
        logger.debug("propName={} propValue={} ", kPropName, vProVal );        
        kcrv.getProperties().put(kPropName, vProVal);
      });
    
    kcrv.setDataObj( Serialization.asYaml( secret.getData() )  );
    //kcrv.setYaml( Serialization.asYaml( secret ) );
    kcrv.setJson( Serialization.asJson( secret ) );

    return kcrv;
  }
  
  
  
  
  
  /*******************
   * 
   * Services Watcher
   * 
   *******************/
  private SharedIndexInformer<Service> createServiceWatcherssForNamespace(String nameSpacename, Map<String, Object> headers) {
    //watcher for secrets
      SharedIndexInformer<Service> shixInformer = this.kubernetesClient.services().inNamespace(nameSpacename).inform(new ResourceEventHandler<Service>() {

            @Override
            public void onAdd(Service obj) {
            logger.debug("Added Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                obj.getKind(), 
                obj.getMetadata().getName(),
                obj.getMetadata().getUid(),
                obj.getMetadata().getNamespace());
            
            headers.forEach(((hname, hval) ->{
              if (hval instanceof String s) {
                if ( hname.contains("org.etsi.osl")) {
                  logger.debug("Header: {} = {} ", hname, s );      
                  if ( obj.getMetadata() == null ) {
                    obj.setMetadata( new ObjectMeta());
                    obj.getMetadata().setLabels( new HashMap<String, String>());
                  }
                  obj.getMetadata().getLabels().put(hname, s);
                }
              }
            }));
            
            updateKubernetesServiceResourceInOSLCatalog( obj, false );     
            
              
            }

            @Override
            public void onUpdate(Service oldObj, Service newObj) {
              logger.debug("onUpdate Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                  newObj.getKind(), 
                  newObj.getMetadata().getName(),
                  newObj.getMetadata().getUid(),
                  newObj.getMetadata().getNamespace());
            //check if namespace is under deletion so to ignore any changes
              if ( kubernetesClientResource.getNameSpacesTobeDeleted().get( newObj.getMetadata().getNamespace()) == null ) {
                updateKubernetesServiceResourceInOSLCatalog( newObj, false );    
              } else {
                updateKubernetesServiceResourceInOSLCatalog( newObj, true );    
              }
              
            }

            @Override
            public void onDelete(Service obj, boolean deletedFinalStateUnknown) {
              logger.debug("onDelete Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                  obj.getKind(), 
                  obj.getMetadata().getName(),
                  obj.getMetadata().getUid(),
                  obj.getMetadata().getNamespace());
              updateKubernetesServiceResourceInOSLCatalog( obj, true );    
              
            }

        
      }, 30 * 1000L); // resync period (set 0 for no resync);
    
      shixInformer.start();
      
      return shixInformer;
    
  }



  private void updateKubernetesServiceResourceInOSLCatalog(Service resource, boolean toDelete) {
//    if (this.informers.size() == 0) {
//      logger.debug("Informers are 0. Will exit updateKubernetesServiceResourceInOSLCatalog");
//      return;
//    }
    ResourceCreate rs = this.KubernetesService2OpensliceResource( resource ).toResourceCreate();
    if (toDelete) {
      rs.setResourceStatus(ResourceStatusType.SUSPENDED);
    }
    catalogClient.createOrUpdateResourceByNameCategoryVersion( rs );  
    
  }

  
  KubernetesService KubernetesService2OpensliceResource(Service serv) {
    
    String baseCRD = String.format( "%s@%s@%s@%s",
        serv.getKind(),
        serv.getApiVersion(),
        kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster(),
        kubernetesClient.getMasterUrl().toExternalForm());

    KubernetesService kcrv = KubernetesService.builder().osl_KUBCRD_RSPEC_UUID( kubernetesServiceResourceSpec.getUuid() )
        .name( serv.getMetadata().getName()  
            + "@" 
            + serv.getMetadata().getNamespace()
            + "@" 
            +  kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster()
            + "@" + 
            kubernetesClient.getMasterUrl().toExternalForm()  )
        .version( serv.getApiVersion() )
        .currentContextCluster( kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster() )
        .clusterMasterURL( kubernetesClient.getMasterUrl().toString()  )
        .fullResourceName( "" )
        .namespace( Serialization.asJson( serv.getMetadata().getNamespace()) )
        .kind( serv.getKind() )
        .apiGroup( serv.getPlural() )
        .uID( serv.getMetadata().getUid() )
        .metadata(Serialization.asJson( serv.getMetadata())  ) //.metadata( gkr.getMetadata().toString() )        
        .description( 
            String.format( "A service in namespace %s on %s", serv.getMetadata().getNamespace(),  baseCRD ))
        .build();


      serv.getMetadata().getLabels().forEach((pk, pv) -> {
        logger.debug("\t label: {} {} ", pk, pv);
        kcrv.getProperties().put( pk  , pv);

    });
    

    kcrv.setSpecObj( Serialization.asYaml( serv.getSpec() )  );
    kcrv.setStatusObj( Serialization.asYaml( serv.getStatus() )  );
    //kcrv.setYaml( Serialization.asYaml( serv ) );
    kcrv.setJson( Serialization.asJson( serv ) );
    
    // Convert YAML to Map<String, String>
    Map<String, String> resultMap = yamlToMap(  kcrv.getSpecObj() );
    kcrv.setSpec(resultMap);
    
    resultMap = yamlToMap(  kcrv.getStatusObj() );
    kcrv.setStatus(resultMap);    

    return kcrv;
  }


  public static Map<String, String> yamlToMap(String yamlString) {
    Yaml yaml = new Yaml();
    Map<String, Object> yamlMap = yaml.load(new StringReader(yamlString));

    // Create a result map that will contain flattened key-value pairs
    Map<String, String> resultMap = new LinkedHashMap<>();
    
    // Call the recursive method to flatten the map
    flattenMap("", yamlMap, resultMap);

    return resultMap;
  }
  
  // Recursive method to flatten the YAML structure
  private static void flattenMap(String prefix, Map<String, Object> sourceMap, Map<String, String> resultMap) {
      for (Map.Entry<String, Object> entry : sourceMap.entrySet()) {
          String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
          Object value = entry.getValue();

          if (value instanceof Map) {
              // Recursively flatten nested maps
              flattenMap(key, (Map<String, Object>) value, resultMap);
          } else {
              // Convert value to String and put it in the resultMap
              resultMap.put(key, value.toString());
          }
      }
  }

  

  /*******************
   * 
   * ConfigMap Watcher
   * 
   *******************/
  private SharedIndexInformer<ConfigMap> createConfigMapWatcherssForNamespace(String nameSpacename, Map<String, Object> headers) {
    //watcher for secrets
      SharedIndexInformer<ConfigMap> shixInformer = this.kubernetesClient.configMaps().inNamespace(nameSpacename).inform(new ResourceEventHandler<ConfigMap>() {

            @Override
            public void onAdd(ConfigMap obj) {
            logger.debug("Added Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                obj.getKind(), 
                obj.getMetadata().getName(),
                obj.getMetadata().getUid(),
                obj.getMetadata().getNamespace());
            
            headers.forEach(((hname, hval) ->{
              if (hval instanceof String s) {
                if ( hname.contains("org.etsi.osl")) {
                  logger.debug("Header: {} = {} ", hname, s );      
                  if ( obj.getMetadata() == null ) {
                    obj.setMetadata( new ObjectMeta());
                    obj.getMetadata().setLabels( new HashMap<String, String>());
                  }
                  obj.getMetadata().getLabels().put(hname, s);
                }
              }
            }));
            
            updateKubernetesConfigMapResourceInOSLCatalog( obj, false );     
            
              
            }

            @Override
            public void onUpdate(ConfigMap oldObj, ConfigMap newObj) {
              logger.debug("onUpdate Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                  newObj.getKind(), 
                  newObj.getMetadata().getName(),
                  newObj.getMetadata().getUid(),
                  newObj.getMetadata().getNamespace());
              //check if namespace is under deletion so to ignore any changes
              if ( kubernetesClientResource.getNameSpacesTobeDeleted().get( newObj.getMetadata().getNamespace()) == null ) {
                updateKubernetesConfigMapResourceInOSLCatalog( newObj, false );                
              } else {
                updateKubernetesConfigMapResourceInOSLCatalog( newObj, true );
              }
                  
              
            }

            @Override
            public void onDelete(ConfigMap obj, boolean deletedFinalStateUnknown) {
              logger.debug("onDelete Namespace watcher Resource Kind:{} Name:{} UID:{} Namespace:{}", 
                  obj.getKind(), 
                  obj.getMetadata().getName(),
                  obj.getMetadata().getUid(),
                  obj.getMetadata().getNamespace());
              updateKubernetesConfigMapResourceInOSLCatalog( obj, true );    
              
            }

        
      }, 30 * 1000L); // resync period (set 0 for no resync);
    
      shixInformer.start();
      
      return shixInformer;
    
  }
  
  private void updateKubernetesConfigMapResourceInOSLCatalog(ConfigMap resource, boolean toDelete) {
//    if (this.informers.size() == 0) {
//      logger.debug("Informers are 0. Will exit updateKubernetesConfigMapResourceInOSLCatalog");
//      return;
//    }
    ResourceCreate rs = this.KubernetesConfigMap2OpensliceResource( resource ).toResourceCreate();
    if (toDelete) {
      rs.setResourceStatus(ResourceStatusType.SUSPENDED);
    }
    catalogClient.createOrUpdateResourceByNameCategoryVersion( rs );  
    
  }

  KubernetesConfigMap KubernetesConfigMap2OpensliceResource(ConfigMap cmap) {

    
    String baseCRD = String.format( "%s@%s@%s@%s",
        cmap.getKind(),
        cmap.getApiVersion(),
        kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster(),
        kubernetesClient.getMasterUrl().toExternalForm());

    KubernetesConfigMap kcrv = KubernetesConfigMap.builder().osl_KUBCRD_RSPEC_UUID( kubernetesConfigMapResourceSpec.getUuid() )
        .name( cmap.getMetadata().getName()  
            + "@" 
            + cmap.getMetadata().getNamespace()
            + "@" 
            +  kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster()
            + "@" + 
            kubernetesClient.getMasterUrl().toExternalForm()  )
        .version( cmap.getApiVersion() )
        .currentContextCluster( kubernetesClient.getConfiguration().getCurrentContext().getContext().getCluster() )
        .clusterMasterURL( kubernetesClient.getMasterUrl().toString()  )
        .fullResourceName( "" )
        .namespace( Serialization.asJson( cmap.getMetadata().getNamespace()) )
        .kind( cmap.getKind() )
        .apiGroup( cmap.getPlural() )
        .uID( cmap.getMetadata().getUid() )
        .metadata(Serialization.asJson( cmap.getMetadata())  ) //.metadata( gkr.getMetadata().toString() )        
        .description( 
            String.format( "A configMap in namespace %s on %s", cmap.getMetadata().getNamespace(),  baseCRD ))
        .build();


    cmap.getMetadata().getLabels().forEach((pk, pv) -> {
      logger.debug("\t label: {} {} ", pk, pv);
      kcrv.getProperties().put( pk  , pv);

    });
    

    kcrv.setDataObj( Serialization.asYaml( cmap.getData() )  );
    //kcrv.setYaml( Serialization.asYaml( cmap ) );
    kcrv.setJson( Serialization.asJson( cmap ) );

    // Convert YAML to Map<String, String>
    Map<String, String> resultMap = yamlToMap(  kcrv.getDataObj() );
    kcrv.setData(resultMap);
    
    return kcrv;
  }


  public void disableNamespace(String ns) {
    this.kubernetesClient.configMaps().inNamespace(ns).delete();
    this.kubernetesClient.secrets().inNamespace(ns).delete();
    this.kubernetesClient.services().inNamespace(ns).delete();

//    for (SharedIndexInformer<?> sharedIndexInformer : informers) {
//      sharedIndexInformer.stop();
//      sharedIndexInformer.close();
//       
//    }
//    this.informers.clear();
    
  }

}
