import {IObjectNode} from '@jacquesparis/objects-model';
import {service} from '@loopback/core';
import {isArray, isObject, isString, merge} from 'lodash';
import {EntityName} from '../../models';
import {ObjectNode} from '../../models/object-node.model';
import {CurrentContext} from '../../services';
import {ObjectNodeDefinitionService} from '../../services/entity-definition/object-node-definition.service';
import {NodeInterceptService} from '../../services/entity-intercept/node-intercept.service';
import {InsideRestService} from '../../services/inside-rest/inside-rest.service';
import {TransientUriReferenceService} from '../../services/inside-rest/transient-uri-reference.service';
import {ObjectNodeService} from '../../services/object-node/object-node.service';
import {TransientEntityService} from '../../services/transient-entity/transient-entity.service';
import {UriCompleteService} from '../../services/uri-complete/uri-complete.service';
import {EntityActionType} from './../../services/application.service';
import {ObjectTypeService} from './../../services/object-type.service';
import {
  CONTENT_GENERIC_PROVIDER,
  TEMPLATE_CONFIGURATION_TYPE,
  TEMPLATE_EMBEDDED_CONFIGURATION_TYPE,
  TEMPLATE_REFERRER_TYPE,
  TEMPLATE_VIEW_TYPE,
} from './content-generic-template.const';

export class TransientContentGenericService {
  constructor(
    @service(TransientEntityService)
    protected transientEntityService: TransientEntityService,
    @service(InsideRestService)
    private insideRestService: InsideRestService,
    @service(ObjectNodeService)
    private objectNodeService: ObjectNodeService,
    @service(ObjectNodeDefinitionService)
    private objectNodeDefinitionService: ObjectNodeDefinitionService,
    @service(ObjectTypeService)
    private objectTypeService: ObjectTypeService,
    @service(UriCompleteService) private uriCompleteService: UriCompleteService,
    @service(TransientUriReferenceService)
    private transientUriReferenceService: TransientUriReferenceService,
    @service(NodeInterceptService)
    private nodeInterceptService: NodeInterceptService,
  ) {
    /*
    this.transientEntityService.registerTransientEntityTypeFunction(
      CONTENT_GENERIC_PROVIDER,
      TransientContentGenericService.name,
      'Add template configuration json schema definitions, configuration from referenced templates',
      EntityName.objectNode,
      TEMPLATE_REFERER_WITH_CONFIGURATION_TYPE.name,
      this.completeTemplateRefererWithConfigurationNode.bind(this),
    );*/

    this.nodeInterceptService.registerEntityInterceptorService(
      CONTENT_GENERIC_PROVIDER,
      TransientContentGenericService.name,
      'Set templateObjectTreeId to parent template',
      TEMPLATE_EMBEDDED_CONFIGURATION_TYPE.name,
      EntityActionType.create,
      this.interceptTemplateEmbeddedConfigurationCreate.bind(this),
    );

    this.transientEntityService.registerTransientEntityTypeFunction(
      CONTENT_GENERIC_PROVIDER,
      TransientContentGenericService.name,
      'Add template configuration json schema definitions to Template Configuration node',
      EntityName.objectNode,
      TEMPLATE_CONFIGURATION_TYPE.name,
      this.completeTemplateConfigurationNode.bind(this),
    );

    this.transientEntityService.registerTransientEntityTypeFunction(
      CONTENT_GENERIC_PROVIDER,
      TransientContentGenericService.name,
      'Add template configuration reference to temaplete referer',
      EntityName.objectNode,
      TEMPLATE_REFERRER_TYPE.name,
      this.completeTemplateRefererNode.bind(this),
    );
  }

  public async interceptTemplateEmbeddedConfigurationCreate(
    entityId: string | undefined,
    entity: ObjectNode,
    requestEntity: Partial<ObjectNode>,
    ctx: CurrentContext,
  ): Promise<boolean> {
    const parentTree = await this.insideRestService.read(
      this.uriCompleteService.getUri(
        EntityName.objectTree,
        entity.parentNodeId as string,
        ctx,
      ),
      ctx,
    );
    requestEntity.templateObjectTreeId = parentTree.aliasId;
    return true;
  }

  async completeTemplateConfigurationNode(
    objectNode: ObjectNode,
    ctx: CurrentContext,
  ) {
    if (objectNode.templateTreeId) {
      let templateNode: ObjectNode = await this.insideRestService.read(
        this.uriCompleteService.getUri(
          EntityName.objectNode,
          objectNode.templateTreeId,
          ctx,
        ),
        ctx,
      );
      if (
        templateNode.contentGenericTemplate &&
        templateNode.contentGenericTemplate.refererConfig &&
        templateNode.contentGenericTemplate.refererConfig.properties
      ) {
        merge(
          objectNode.entityCtx?.jsonSchema?.properties.configuration.properties,
          templateNode.contentGenericTemplate.refererConfig.properties,
        );
        await this.objectNodeDefinitionService.completeProperties(
          objectNode.entityCtx?.jsonSchema?.properties.configuration.properties,
          ctx.nodeContext,
        );
      }
    }
  }

  public async completeTemplateRefererNode(
    objectNode: ObjectNode,
    ctx: CurrentContext,
  ) {
    if (objectNode.entityCtx?.jsonSchema?.properties) {
      await this.addConfigurationRefernceToProperties(
        objectNode.entityCtx.jsonSchema.properties,
        ctx,
      );
    }
    await this.addTemplatesConfigurations(objectNode, ctx);
  }

  private async addTemplatesConfigurations(
    objectNode: any,
    ctx: CurrentContext,
  ): Promise<void> {
    const templateTypes = await this.objectTypeService.getImplementingTypes(
      TEMPLATE_VIEW_TYPE.name,
    );
    for (const key of Object.keys(objectNode)) {
      if (isArray(objectNode[key])) {
        for (const subObjectNode of objectNode[key]) {
          if (isObject(subObjectNode)) {
            await this.addTemplatesConfigurations(subObjectNode, ctx);
          }
        }
      } else if (isObject(objectNode[key])) {
        for (const subObjectNode of Object.values(objectNode[key])) {
          if (isObject(subObjectNode)) {
            await this.addTemplatesConfigurations(subObjectNode, ctx);
          }
        }
      } else if (isString(objectNode[key]) && key.endsWith('ObjectTreeId')) {
        const refParts: string[] = objectNode[key].split('/');
        const refType: string = refParts[refParts.length - 2];
        if (templateTypes.includes(refType)) {
          const refTemplate = key.replace('ObjectTreeId', 'Tree');
          const refConfiguration = key.replace(
            'ObjectTreeId',
            'ConfigurationTree',
          );
          const refConfigurationId = key.replace(
            'ObjectTreeId',
            'ConfigurationObjectTreeId',
          );
          if (objectNode[refConfigurationId]) {
            if (!objectNode[refTemplate]) {
              objectNode[refTemplate] = await this.insideRestService.read(
                this.uriCompleteService.getUri(
                  EntityName.objectTree,
                  objectNode[key],
                  ctx,
                ),
                ctx,
              );
            }
            if (!objectNode[refConfiguration]) {
              objectNode[refConfiguration] = await this.insideRestService.read(
                this.uriCompleteService.getUri(
                  EntityName.objectTree,
                  objectNode[refConfigurationId],
                  ctx,
                ),
                ctx,
              );
            }
            if (objectNode[refTemplate] && objectNode[refConfiguration]) {
              if (!objectNode.templatesConfigurations) {
                objectNode.templatesConfigurations = {};
              }
              const config = await this.insideRestService.read<IObjectNode>(
                objectNode[refConfiguration].treeNode.uri,
                ctx,
              );
              objectNode.templatesConfigurations[
                objectNode[refTemplate].treeNode.name
              ] = config.configuration;
            }
          }
        }
      }
    }
  }

  private async addConfigurationRefernceToProperties(
    properties: any,
    ctx: CurrentContext,
  ) {
    const templateTypes = await this.objectTypeService.getImplementingTypes(
      TEMPLATE_VIEW_TYPE.name,
    );
    for (const key of Object.keys(properties)) {
      if ('object' === properties[key].type && properties[key].properties) {
        await this.addConfigurationRefernceToProperties(
          properties[key].properties,
          ctx,
        );
      } else if ('array' === properties[key].type && properties[key].items) {
        await this.addConfigurationRefernceToProperties(properties[key], ctx);
      } else if (
        'string' === properties[key].type &&
        properties[key].oneOf &&
        0 < properties[key].oneOf.length &&
        key.endsWith('ObjectTreeId')
      ) {
        const refParts: string[] = properties[key].oneOf[0].enum[0].split('/');
        const refType: string = refParts[refParts.length - 2];
        if (templateTypes.includes(refType)) {
          const configurationType = refType + 'Configuration';
          const configurationKey = key.replace(
            'ObjectTreeId',
            'ConfigurationObjectTreeId',
          );
          if (await this.objectTypeService.searchById(configurationType)) {
            properties[configurationKey] = {
              type: 'string',
              title: properties[key].title + ' Configuration',
              default: '',
              oneOfTree: [
                {
                  treeType: configurationType,
                  namespaceName: 'templates',
                  namespaceType: 'RepositoryCategory',
                  ownerName: 'public',
                  ownerType: 'Repository',
                },
                {
                  treeType: configurationType,
                },
              ],
            };
            await this.objectNodeDefinitionService.completeProperties(
              properties,
              ctx.nodeContext,
            );
          }
        }
      }
    }
  }

  /*
  async completeTemplateRefererWithConfigurationNode(
    objectNode: ObjectNode,
    ctx: CurrentContext,
  ) {
    for (let key of Object.keys(objectNode)) {
      if (
        key + 'Id' in objectNode &&
        objectNode[key].entityCtx &&
        -1 <
          indexOf(
            objectNode[key].entityCtx.implementedTypes,
            TEMPLATE_VIEW_TYPE.name,
          )
      ) {
        if (key.endsWith('Tree')) {
          key = key.substr(0, key.length - 4) + 'ObjectTree';
        } else if (key.endsWith('Node')) {
          key = key.substr(0, key.length - 4) + 'ObjectNode';
        } else if (key.endsWith('Type')) {
          key = key.substr(0, key.length - 4) + 'ObjectType';
        }
        await this.addTemplateConfiguration(
          objectNode.entityCtx?.jsonSchema,
          key + 'Id',
          'model',
          ctx,
        );
      }
    }
  }
*/

  /*
  async addTemplateConfiguration(
    templateRefJsonSchema: IJsonSchema | undefined,
    templateKeyId: string,
    templateParentJsonPath: string,
    ctx: CurrentContext,
  ) {
    if (
      templateRefJsonSchema?.properties &&
      templateRefJsonSchema.properties[templateKeyId] &&
      isArray(templateRefJsonSchema.properties[templateKeyId].oneOf) &&
      0 < templateRefJsonSchema.properties[templateKeyId].oneOf.length
    ) {
      for (const templateEnum of templateRefJsonSchema.properties[templateKeyId]
        .oneOf) {
        const templateTreeId = templateEnum.enum[0];
        const template = ctx.nodeContext.references[templateTreeId]?.value;
        if (template) {
          if (!template.contentGenericTemplate) {
            template.contentGenericTemplate = {};
            await this.transientEntityService.completeReturnedEntity(
              EntityName.objectNode,
              template,
              CurrentContext.get(ctx, {
                nodeContext: {
                  node: ctx.nodeContext.references[templateTreeId],
                },
              }),
            );
          }
          await this.addTemplateConfigurationProperties(
            template,
            templateParentJsonPath,
            'model.' + templateKeyId,
            templateTreeId,
            templateRefJsonSchema,
            ctx,
          );
        }
      }
    }
  }*/

  /*  private async addTemplateConfigurationProperties(
    templateView: ObjectNode,
    modelPath: string,
    expressionPath: string,
    templateObjectTreeId: string,
    jsonSchema: IJsonSchema | undefined,
    ctx: CurrentContext,
  ) {
    if (
      jsonSchema &&
      templateView?.contentGenericTemplate?.refererConfig?.properties &&
      0 <
        Object.keys(
          templateView?.contentGenericTemplate?.refererConfig?.properties,
        ).length
    ) {
      if (!jsonSchema.properties.templatesConfigurations) {
        jsonSchema.properties.templatesConfigurations = {
          type: 'object',
          title: 'Object template configurattion',
          'x-schema-form': {labelHtmlClass: 'd-none'},
          properties: {},
        };
      }
      if (jsonSchema?.properties?.templatesConfigurations.properties) {
        jsonSchema.properties.templatesConfigurations.properties[
          templateView.name
        ] = merge(
          {
            type: 'object',
            'x-schema-form': {labelHtmlClass: 'd-none'},
          },
          templateView.contentGenericTemplate.refererConfig,
        );
        addCondition(
          expressionPath + "=='" + templateObjectTreeId + "'",
          jsonSchema.properties.templatesConfigurations.properties[
            templateView.name
          ],
          modelPath,
        );

        if (
          jsonSchema.properties.templatesConfigurations.properties[
            templateView.name
          ].properties
        ) {
          await this.objectNodeDefinitionService.completeProperties(
            jsonSchema.properties.templatesConfigurations.properties[
              templateView.name
            ].properties,
            ctx.nodeContext,
          );
        }
      }
    }
  }*/
}
