import {
  IJsonSchema,
  IMethodResult,
  IObjectNode,
  IObjectTree,
} from '@jacquesparis/objects-model';
import {
  AjaxResult,
  WebsiteGenerationService,
} from '@jacquesparis/objects-website';
import {service} from '@loopback/core';
import {ApplicationError, contentGenericTemplate} from '../../helper';
import {
  GeneratedResponse,
  TextGeneratedResponse,
} from '../../helper/generated-response';
import {ObjectNode} from '../../models';
import {ObjectTypeService} from '../../services';
import {ActionEntityService} from '../../services/action-entity/action-entity.service';
import {ObjectNodeService} from '../../services/object-node/object-node.service';
import {ServerConfigService} from '../server-config/server-config.service';
import {
  HtmlGeneratedResponse,
  JsonGeneratedResponse,
} from './../../helper/generated-response';
import {EntityName} from './../../models/entity-name';
import {ObjectTree} from './../../models/object-tree.model';
import {
  CurrentContext,
  ExpectedValue,
} from './../../services/application.service';
import {MustacheService} from './../../services/entity-definition/mustache.service';
import {InsideRestService} from './../../services/inside-rest/inside-rest.service';
import {UriCompleteService} from './../../services/uri-complete/uri-complete.service';
import {
  PAGE_TYPE,
  PAGE_WITH_SUB_PAGE_TYPE,
  WEB_SITE_PROVIDER,
  WEB_SITE_VIEW_TYPE,
} from './web-site.const';
import {Popup} from './web-site.interface';
import {WebSiteService} from './web-site.service';

export class ActionWebSiteService {
  protected doc: {
    templatesMustache: {[templateId: string]: string};
    templateMustache: string;
    headerScript: string;
    footerScript: string;
    templateAngular: string;
    scss: string;
    css: string;
    controller: string;
    refererConfig: IJsonSchema;
  };
  protected websiteGenerationService: WebsiteGenerationService;
  constructor(
    @service(ActionEntityService)
    protected actionEntityService: ActionEntityService,
    @service(ObjectNodeService)
    private objectNodeService: ObjectNodeService,
    @service(UriCompleteService) private uriCompleteService: UriCompleteService,
    @service(InsideRestService) private insideRestService: InsideRestService,
    @service(MustacheService) private mustacheService: MustacheService,
    @service(WebSiteService) private webSiteService: WebSiteService,
    @service(ServerConfigService)
    private serverConfigService: ServerConfigService,
    @service(ObjectTypeService)
    private objectTypeService: ObjectTypeService,
  ) {
    this.websiteGenerationService = WebsiteGenerationService.get();
    this.actionEntityService.registerNewViewFunction(
      WEB_SITE_PROVIDER,
      ActionWebSiteService.name,
      'Build ajax view',
      EntityName.objectTree,
      'ajax',
      WEB_SITE_VIEW_TYPE.name,
      this.ajaxWebSiteViewTree.bind(this),
      'read',
    );
    this.actionEntityService.registerNewViewFunction(
      WEB_SITE_PROVIDER,
      ActionWebSiteService.name,
      'Build html view',
      EntityName.objectTree,
      'html',
      WEB_SITE_VIEW_TYPE.name,
      this.htmlWebSiteViewTree.bind(this),
      'read',
    );
    this.actionEntityService.registerNewViewFunction(
      WEB_SITE_PROVIDER,
      ActionWebSiteService.name,
      'Build popup view',
      EntityName.objectTree,
      'popup',
      WEB_SITE_VIEW_TYPE.name,
      this.popupWebSiteViewTree.bind(this),
      'read',
    );

    this.actionEntityService.registerNewMethodFunction<ObjectNode>(
      WEB_SITE_PROVIDER,
      ActionWebSiteService.name,
      'Add text in an existing paragraph',
      EntityName.objectNode,
      'addText',
      PAGE_WITH_SUB_PAGE_TYPE.name,
      this.addText.bind(this),
      'update',
    );

    this.actionEntityService.registerNewMethodFunction<ObjectNode>(
      WEB_SITE_PROVIDER,
      ActionWebSiteService.name,
      'Add paragraph in an existing paragraph',
      EntityName.objectNode,
      'addParagraph',
      PAGE_WITH_SUB_PAGE_TYPE.name,
      this.addParagraph.bind(this),
      'update',
    );

    this.actionEntityService.registerNewMethodFunction<ObjectNode>(
      WEB_SITE_PROVIDER,
      ActionWebSiteService.name,
      'Add event entry',
      EntityName.objectNode,
      'addEvent',
      PAGE_WITH_SUB_PAGE_TYPE.name,
      this.addEvent.bind(this),
      'update',
    );
    this.doc = contentGenericTemplate(__dirname, 'doc');
  }

  public async popupWebSiteViewTree(
    entity: ObjectTree,
    args: {0?: string},
    ctx: CurrentContext,
  ): Promise<JsonGeneratedResponse<Popup>> {
    const objectNode = await this.objectNodeService.getNode(
      args[0] as string,
      CurrentContext.get(ctx, {}),
    );
    return new JsonGeneratedResponse<Popup>(
      await this.webSiteService.getPopupContent(
        entity.treeNode.popupLinkLabels,
        objectNode,
        ctx,
      ),
    );
  }

  public async htmlWebSiteViewTree(
    entity: ObjectTree,
    args: {0?: string; 1?: string; 2?: string},
    ctx: CurrentContext,
  ): Promise<GeneratedResponse> {
    const result: JsonGeneratedResponse<AjaxResult> = await this.ajaxWebSiteViewTree(
      entity,
      args,
      ctx,
    );
    result.json.headerScripts.pageScript = `
    function getPageHref(page) {
      let baseUri = '${this.uriCompleteService.getUri(
        EntityName.objectTree,
        entity.id,
        ctx,
      )}/view/html';
      if (page && page.parentPage) {
        return baseUri + '/' +
          page.parentPage.pageId +
          '#' +
          page.parentPage.anchor;
      }
      return baseUri +
        (page ? '/' + page.treeNode.id : '');
    }
    `;
    return this.getHtmlDocResponseFromAjaxResult(ctx, result);
  }
  public getHtmlDocResponseFromAjaxResult(
    ctx: CurrentContext,
    response: GeneratedResponse,
  ): HtmlGeneratedResponse {
    return new HtmlGeneratedResponse(
      ctx,
      this.getHtmlDocFromAjaxResult(ctx, response),
    );
  }

  public getHtmlDocFromAjaxResult(
    ctx: CurrentContext,
    response: GeneratedResponse,
  ): string {
    let result: string = (undefined as unknown) as string;
    if (response instanceof TextGeneratedResponse) {
      result = this.mustacheService.parse(this.doc.templateMustache, {
        body: response.response,
        headerScript: this.doc.headerScript,
        headerTags: {
          refIcon: `<link rel="icon" type="image/png" href="${ctx.webSiteContext.pageBaseUri.value.replace(
            '/' + ctx.uriContext.uri.value.acceptLanguage + '/',
            '/favicon',
          )}.png">`,
        },
      });
    } else if (response instanceof JsonGeneratedResponse) {
      const docParts: AjaxResult & {headerScript: string; lang: string} =
        response.json;
      docParts.headerScript = this.doc.headerScript;
      if (!docParts.headerTags.refIcon) {
        docParts.headerTags.refIcon = `<link rel="icon" type="image/png" href="${ctx.webSiteContext.pageBaseUri.value.replace(
          '/' + ctx.uriContext.uri.value.acceptLanguage + '/',
          '/favicon',
        )}.png">`;
        docParts.lang = ctx.uriContext.uri.value.acceptLanguage.substring(0, 2);
      }

      result = this.mustacheService.parse(this.doc.templateMustache, docParts);
    }
    return result;
  }

  public getPageHref(
    page: IObjectTree,
    site: ObjectTree,
    ctx: CurrentContext,
  ): string {
    const viewId =
      site.id +
      '/view/html' +
      (page
        ? '/' +
          (page.parentPage
            ? page.parentPage.pageId
            : (page.treeNode as IObjectNode).id)
        : '') +
      (page && page.parentPage ? '#' + page.parentPage.anchor : '');
    return this.uriCompleteService.getUri(EntityName.objectTree, viewId, ctx);
  }

  public getPopupHref(
    page: IObjectTree,
    site: ObjectTree,
    ctx: CurrentContext,
  ): string {
    const viewId =
      site.id +
      '/view/popup' +
      (page ? '/' + (page.treeNode as IObjectNode).id : '');
    return this.uriCompleteService.getUri(EntityName.objectTree, viewId, ctx);
  }
  public getAdminHref(
    page: IObjectTree,
    site: ObjectTree,
    ctx: CurrentContext,
  ): string {
    return (
      '/admin/#/admin/owner/' +
      page.ownerType +
      '/' +
      page.ownerName +
      '/namespace/' +
      page.namespaceType +
      '/' +
      page.namespaceName
    );
  }

  public async getWebSiteAjaxResponse(
    webSiteTree: ObjectTree,
    getPageHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string,
    getAdminHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string,
    getPopupHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string,
    ctx: CurrentContext,
  ) {
    return this.ajaxWebSiteViewTree(
      webSiteTree,
      {},
      ctx,
      getPageHref,
      getAdminHref,
      getPopupHref,
    );
  }

  public async getWebSitePageAjaxResponse(
    webSiteTree: ObjectTree,
    pageNodeId: string,
    getPageHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string,
    getAdminHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string,
    getPopupHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string,
    ctx: CurrentContext,
  ) {
    return this.ajaxWebSiteViewTree(
      webSiteTree,
      {0: pageNodeId},
      ctx,
      getPageHref,
      getAdminHref,
      getPopupHref,
    );
  }

  public async ajaxWebSiteViewTree(
    entity: ObjectTree,
    args: {0?: string; 1?: string; 2?: string},
    ctx: CurrentContext,
    getPageHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string = this.getPageHref.bind(this),
    getAdminHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string = this.getAdminHref.bind(this),
    getPopupHref: (
      page: IObjectTree,
      site: ObjectTree,
      ctx: CurrentContext,
    ) => string = this.getPopupHref.bind(this),
  ): Promise<JsonGeneratedResponse<AjaxResult>> {
    ctx.webSiteContext.generationContext.debug = await this.serverConfigService.get(
      'debugGeneration',
      () => Promise.resolve(false),
    );
    const ajaxResult: AjaxResult = await this.websiteGenerationService.getAjaxContent(
      {
        getCachedOrRemoteObjectById: async (
          treeId: string,
        ): Promise<IObjectTree> => {
          try {
            return (await this.insideRestService.read(
              this.uriCompleteService.getUri(
                EntityName.objectTree,
                treeId,
                ctx,
              ),
              ctx,
            )) as IObjectTree;
          } catch (error) {
            return {children: [], treeNode: {name: 'unknown'}};
          }
        },
      },
      {
        getCachedOrRemoteObjectById: async (
          nodeId: string,
        ): Promise<IObjectNode> => {
          try {
            return (await this.insideRestService.read(
              this.uriCompleteService.getUri(
                EntityName.objectNode,
                nodeId,
                ctx,
              ),
              ctx,
            )) as IObjectNode;
          } catch (error) {
            return {name: 'unknown'};
          }
        },
      },
      {
        getPageHref: (page: IObjectTree): string => {
          return getPageHref(page, entity, ctx);
        },
        getAdminHref: (page: IObjectTree): string => {
          return getAdminHref(page, entity, ctx);
        },
        getPopupHref: (page: IObjectTree): string => {
          return getPopupHref(page, entity, ctx);
        },
      },
      ctx.webSiteContext.generationContext,
      entity.id,
      args[0],
      args[1],
      args[2],
    );

    return new JsonGeneratedResponse<AjaxResult>(ajaxResult);
  }

  public async addText(
    objectNode: ObjectNode,
    args: {text?: string},
    ctx: CurrentContext,
  ): Promise<IMethodResult> {
    if (!args.text) {
      throw ApplicationError.missingParameter('text');
    }
    const objectType = await ctx.nodeContext.objectType.getOrSetValue(
      async () => {
        return this.objectTypeService.searchById(
          objectNode.objectTypeId as string,
        );
      },
    );
    let childObjectType;
    for (const subType of objectType.objectSubTypes) {
      if (
        (
          await this.objectTypeService.getImplementedTypes(
            subType.subObjectTypeId,
          )
        ).includes(PAGE_TYPE.name)
      ) {
        childObjectType = subType.subObjectTypeId;
        break;
      }
    }
    if (!childObjectType) {
      throw ApplicationError.incompatible({
        parentType: objectNode.objectTypeId,
        childType: PAGE_TYPE.name,
      });
    }
    args.text = args.text.replace(/\r\n/, '\n');
    args.text = args.text.replace(/\n\r/, '\n');
    args.text = args.text.replace(/\r/, '\n');
    const texts = args.text.split('\n');
    for (const text of texts) {
      if (text) {
        await this.objectNodeService.add(
          {
            parentNodeId: objectNode.id,
            objectTypeId: childObjectType,
            contentText: text,
          },
          CurrentContext.get(ctx, {
            nodeContext: {parent: new ExpectedValue(objectNode)},
          }),
          false,
          true,
          true,
        );
      }
    }
  }

  public async addEvent(
    objectNode: ObjectNode,
    args: {title?: string; [key: string]: any},
    ctx: CurrentContext,
  ): Promise<IMethodResult> {
    if (!args.title) {
      throw ApplicationError.missingParameter('title');
    }
    const objectType = await ctx.nodeContext.objectType.getOrSetValue(
      async () => {
        return this.objectTypeService.searchById(
          objectNode.objectTypeId as string,
        );
      },
    );
    let childObjectType;
    for (const subType of objectType.objectSubTypes) {
      if (
        (
          await this.objectTypeService.getImplementedTypes(
            subType.subObjectTypeId,
          )
        ).includes(PAGE_TYPE.name)
      ) {
        childObjectType = subType.subObjectTypeId;
        break;
      }
    }
    if (!childObjectType) {
      throw ApplicationError.incompatible({
        parentType: objectNode.objectTypeId,
        childType: PAGE_TYPE.name,
      });
    }
    const eventNode: Partial<ObjectNode> = {
      parentNodeId: objectNode.id,
      objectTypeId: childObjectType,
      title: args.title,
      menuTitle: args.title,
      paragraphTitle: args.title,
    };
    await this.webSiteService.completeEventObjectNodeCreation(eventNode, args);
    return this.objectNodeService.add(eventNode, ctx, false, true, true);
  }

  public async addParagraph(
    objectNode: ObjectNode,
    args: {title?: string; text?: string},
    ctx: CurrentContext,
  ): Promise<IMethodResult> {
    if (!args.title) {
      return this.addText(objectNode, args, ctx);
    }
    const objectType = await ctx.nodeContext.objectType.getOrSetValue(
      async () => {
        return this.objectTypeService.searchById(
          objectNode.objectTypeId as string,
        );
      },
    );
    let childObjectType;
    for (const subType of objectType.objectSubTypes) {
      if (
        (
          await this.objectTypeService.getImplementedTypes(
            subType.subObjectTypeId,
          )
        ).includes(PAGE_TYPE.name)
      ) {
        childObjectType = subType.subObjectTypeId;
        break;
      }
    }
    if (!childObjectType) {
      throw ApplicationError.incompatible({
        parentType: objectNode.objectTypeId,
        childType: PAGE_TYPE.name,
      });
    }

    const parentNode = await this.objectNodeService.add(
      {
        parentNodeId: objectNode.id,
        objectTypeId: childObjectType,
        title: args.title,
        paragraphTitle: args.title,
      },
      ctx,
      false,
      true,
      true,
    );
    return this.addText(
      parentNode,
      args,
      CurrentContext.get(ctx, {
        nodeContext: {node: new ExpectedValue(parentNode)},
      }),
    );
  }
}
