/**
 * This module must not import anything globally not workin in web-mode! if needed, require it within the functions
 *
 * NOTE, we Ignore the infrastructure-scripts libraries when bundling, so these can be used ...
 * We also put fs to empty! If you need other libs, add them to `node: { fs: empty }`
 */
import { isServiceOrientedApp } from './soa-component';
import { resolveAssetsPath } from '../libs/iso-libs';
import * as deepmerge from 'deepmerge';
import { IConfigParseResult } from '../libs/config-parse-result';
import {IPlugin, forwardChildIamRoleStatements} from '../libs/plugin';
import { PARSER_MODES } from '../libs/parser';

import extractDomain from 'extract-domain';

/**
 * Parameters that apply to the whole Plugin, passed by other plugins
 */
export interface ISoaPlugin {

    /**
     * the stage is the environment to apply
     */
    stage: string,

    /**
     * one of the [[PARSER_MODES]]
     */
    parserMode: string,

    /**
     * path to a directory where we put the final bundles
     */
    buildPath: string,

    /**
     * path to the main config file
     */
    configFilePath: string
}

/**
 * A Plugin to detect SinglePage-App-Components
 * @param props
 */
export const SoaPlugin = (props: ISoaPlugin): IPlugin => {

    //console.log("configFilePath: " , props.configFilePath);

    const result: IPlugin = {
        // identify Isomorphic-App-Components
        applies: (component): boolean => {

            return isServiceOrientedApp(component);
        },

        // convert the component into configuration parts
        process: (
            component: any,
            childConfigs: Array<IConfigParseResult>,
            infrastructureMode: string | undefined
    ): IConfigParseResult => {

            console.log("services: ", component.services);
            const path = require('path');

            // we use the hardcoded name `server` as name
            const serverName = "server";

            const serverBuildPath = path.join(require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(), props.buildPath);



            // the service-oriented app has a server application
            const serverWebPack = require("../../../infrastructure-scripts/dist/infra-comp-utils/webpack-libs").complementWebpackConfig(
                require("../../../infrastructure-scripts/dist/infra-comp-utils/webpack-libs").createServerWebpackConfig(
                    "./"+path.join("node_modules", "infrastructure-components", "dist" , "assets", "soa-server.js"), //entryPath: string,
                    serverBuildPath, //use the buildpath from the parent plugin
                    serverName, // name of the server
                    {
                        __CONFIG_FILE_PATH__: require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").pathToConfigFile(props.configFilePath), // replace the IsoConfig-Placeholder with the real path to the main-config-bundle

                        // required of data-layer, makes the context match!
                        "infrastructure-components": path.join(
                            require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(),
                            "node_modules", "infrastructure-components", "dist", "index.js"),

                        // required of the routed-app
                        "react-router-dom": path.join(
                            require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(),
                            "node_modules", "react-router-dom"),

                        // required of the data-layer / apollo
                        "react-apollo": path.join(
                            require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(),
                            "node_modules", "react-apollo"),
                    }, {
                        __SERVICEORIENTED_ID__: `"${component.instanceId}"`,
                        __ISOFFLINE__: props.parserMode === PARSER_MODES.MODE_START,
                        //__ASSETS_PATH__: `"${component.assetsPath}"`,
                        __DATALAYER_ID__: `"${component.dataLayerId}"`,

                        /*__RESOLVED_ASSETS_PATH__: `"${resolveAssetsPath(
                            component.buildPath,
                            serverName,
                            component.assetsPath )
                            }"`*/

                        // TODO add replacements of datalayers here!
                    },
                ),
                props.parserMode === PARSER_MODES.MODE_DEPLOY //isProd
            );

            
            const webappBuildPath = path.join(require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(), props.buildPath);

            const soaWebPack = require("../../../infrastructure-scripts/dist/infra-comp-utils/webpack-libs")
                .complementWebpackConfig(require("../../../infrastructure-scripts/dist/infra-comp-utils/webpack-libs")
                    .createClientWebpackConfig(
                        "./"+path.join("node_modules", "infrastructure-components", "dist" , "assets", "soa.js"), //entryPath: string,
                        path.join(require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(), props.buildPath), //use the buildpath from the parent plugin
                        component.id, //appName
                        undefined, //assetsPath
                        undefined, // stagePath: TODO take from Environment!
                        {
                            __CONFIG_FILE_PATH__: require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").pathToConfigFile(props.configFilePath), // replace the IsoConfig-Placeholder with the real path to the main-config-bundle
                            
                            // required of the routed-app
                            "react-router-dom": path.join(
                                require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(),
                                "node_modules", "react-router-dom"),

                            // required of the data-layer / apollo
                            "react-apollo": path.join(
                                require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(),
                                "node_modules", "react-apollo"),
                        }, {
                        }
                    ),
                    props.parserMode === PARSER_MODES.MODE_DEPLOY //isProd
                );
            

            // provide all client configs in a flat list
            const webpackConfigs: any = childConfigs.reduce((result, config) => result.concat(config.webpackConfigs), []);

            const copyAssetsPostBuild = () => {
                //console.log("check for >>copyAssetsPostBuild<<");
             /*   if (props.parserMode !== PARSER_MODES.MODE_DOMAIN && props.parserMode !== PARSER_MODES.MODE_DEPLOY) {
                    // always copy the assets, unless we setup the domain
                    console.log("copyAssetsPostBuild: now copy the assets!");

                    webpackConfigs.map(config => require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").copyAssets( config.output.path, path.join(serverBuildPath, serverName, component.assetsPath)));

                } else {
                    // delete the assets folder for we don't want to include all these bundled files in the deployment-package
                    const rimraf = require("rimraf");
                    rimraf.sync(path.join(serverBuildPath, serverName, component.assetsPath));

                }
                */
            };

            const environments = childConfigs.reduce((result, config) => (result !== undefined ? result : []).concat(config.environments !== undefined ? config.environments : []), []);

            // check whether we already created the domain of this environment
            const deployedDomain = process.env[`DOMAIN_${props.stage}`] !== undefined;


            const domain = childConfigs.map(config => config.domain).reduce((result, domain) => result !== undefined ? result : domain, undefined);
            const certArn = childConfigs.map(config => config.certArn).reduce((result, certArn) => result !== undefined ? result : certArn, undefined);


            const stagePath = props.parserMode === PARSER_MODES.MODE_DEPLOY &&
                domain == undefined &&
                environments !== undefined &&
                environments.length > 0 ? environments[0].name : undefined;

            const createHtml = ( { serviceEndpoints }) => {

                //console.log("check for >>copyAssetsPostBuild<<");
                //if (props.parserMode == PARSER_MODES.MODE_BUILD) {
                console.log("write the index.html!");
                console.log("serviceEndpoints: ", serviceEndpoints);


                // we need to get rid of the path of the endpoint
                const servicePath = serviceEndpoints && serviceEndpoints.length > 0 ? (
                    stagePath ?
                        // when we have a stagePath, we can remove anything behind it
                        serviceEndpoints[0].substr(0, serviceEndpoints[0].indexOf(stagePath)+stagePath.length) :
                        // when we don't have a stagePath - TODO
                        serviceEndpoints[0]
                ) : undefined;


                
                console.log ("servicePath: " , servicePath);


                // TODO this should not be hard-coded
                const graphqlUrl = component.dataLayerId ? (
                    props.parserMode === PARSER_MODES.MODE_START ? "http://localhost:3001/query" : servicePath+"/query"
                ) : undefined;

                //region: 'localhost',
                //endpoint: 'http://localhost:8000',

                require('fs').writeFileSync(path.join(webappBuildPath, component.stackName, "index.html"), `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>${component.stackName}</title>
    <style>
            body {
                display: block;
                margin: 0px;
            }
         </style>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script>
        ${graphqlUrl !== undefined ? `window.__GRAPHQL__ ="${graphqlUrl}"` : ""};
        ${servicePath !== undefined ? `window.__BASENAME__ ="${servicePath}"` : ""};
    </script>
    <script src="${component.stackName}.bundle.js"></script>
  </body>
</html>`);



            };




            const invalidateCloudFrontCache = () => {
                if (deployedDomain && props.parserMode === PARSER_MODES.MODE_DEPLOY) {
                    require("../../../infrastructure-scripts/dist/infra-comp-utils/sls-libs").invalidateCloudFrontCache(domain);
                }
            }

            const hostedZoneName = domain !== undefined ? extractDomain(domain.toString()) : {};


            /** post build function to write to the .env file that the domain has been deployed */
            const writeDomainEnv = () => {
                //console.log("check for >>writeDomainEnv<<");

                // we only write to the .env file when we are in domain mode, i.e. this script creates the domain
                // and we did not yet deployed the domain previously
                if (!deployedDomain && props.parserMode === PARSER_MODES.MODE_DOMAIN) {
                    require('fs').appendFileSync(
                        path.join(
                            require("../../../infrastructure-scripts/dist/infra-comp-utils/system-libs").currentAbsolutePath(),
                            ".env"),
                        `\nDOMAIN_${props.stage}=TRUE`
                    );
                }
            };

            /*
            const postDeploy = async () => {
                //console.log("check for >>showStaticPageName<<");
                if (props.parserMode === PARSER_MODES.MODE_DEPLOY) {


                    await require('../libs/scripts-libs').fetchData("deploy", {
                        proj: component.stackName,
                        envi: props.stage,
                        domain: domain,
                        endp: `http://${component.stackName}-${props.stage}.s3-website-${component.region}.amazonaws.com`
                    });

                    console.log(`Your SinglePageApp is now available at: http://${component.stackName}-${props.stage}.s3-website-${component.region}.amazonaws.com`);
                }

                
            };*/

            async function deployWithDomain() {
                // start the sls-config
                if (props.parserMode === PARSER_MODES.MODE_DOMAIN) {
                    await require("../../../infrastructure-scripts/dist/infra-comp-utils/sls-libs").deploySls(component.stackName);
                }
            }

            const additionalStatements: Array<any> = forwardChildIamRoleStatements(childConfigs).concat(
                component.iamRoleStatements ? component.iamRoleStatements : []
            );

            const iamRoleAssignment = {
                functions: {}
            };

            iamRoleAssignment.functions[serverName] = {
                role: "ServiceOrientedAppLambdaRole"
            }


            const iamPermissions = {

                resources: {
                    Resources: {
                        ServiceOrientedAppLambdaRole: {
                            Type: "AWS::IAM::Role",

                            Properties: {
                                RoleName: "${self:service}-${self:provider.stage, env:STAGE, 'dev'}-ServiceOrientedAppLambdaRole",
                                AssumeRolePolicyDocument: {
                                    Version: '"2012-10-17"',
                                    Statement: [
                                        {
                                            Effect: "Allow",
                                            Principal: {
                                                Service: ["lambda.amazonaws.com"]
                                            },
                                            Action: "sts:AssumeRole"
                                        }
                                    ]
                                },
                                Policies: [
                                    {
                                        PolicyName: "${self:service}-${self:provider.stage, env:STAGE, 'dev'}-ServiceOrientedAppLambdaPolicy",
                                        PolicyDocument: {
                                            Version: '"2012-10-17"',
                                            Statement: [
                                                {
                                                    Effect: "Allow",
                                                    Action: [
                                                        '"logs:*"',
                                                        '"cloudwatch:*"'
                                                    ],
                                                    Resource: '"*"'
                                                }, {
                                                    Effect: "Allow",
                                                    Action: [
                                                        "s3:Get*",
                                                        "s3:List*",
                                                    ],
                                                    Resource: '"*"'
                                                },

                                            ].concat(additionalStatements)
                                        }
                                    }
                                ]
                            }
                        },
                    },
                }
            }

            // TODO this should rather be put into DataLayer-Plugin!!!
            const dataLayerService = component.dataLayerId !== undefined ? [{
                method: "ANY",
                path: "query"
            }] : [];

            /**
             * ONLY add the domain config if we are in domain mode!
             * TODO once the domain has been added, we need to add this with every deployment
             */
            const domainConfig = (props.parserMode === PARSER_MODES.MODE_DOMAIN || deployedDomain) &&
                    domain !== undefined && certArn !== undefined ? {

                // required of the SPA-domain-alias
                provider: {
                    customDomainName: domain,
                    hostedZoneName: hostedZoneName,
                    certArn: certArn
                },

                resources: {
                    Resources: {
                        WebAppCloudFrontDistribution: {
                            Type: "AWS::CloudFront::Distribution",
                            Properties: {
                                DistributionConfig: {
                                    Origins: [
                                        {
                                            DomainName: "${self:provider.staticBucket}.s3.amazonaws.com",
                                            Id: component.stackName,
                                            CustomOriginConfig: {
                                                HTTPPort: 80,
                                                HTTPSPort: 443,
                                                OriginProtocolPolicy: "https-only",
                                            }
                                        }
                                    ],
                                    Enabled: "'true'",

                                    DefaultRootObject: "index.html",
                                    CustomErrorResponses: [{
                                        ErrorCode: 404,
                                        ResponseCode: 200,
                                        ResponsePagePath: "/index.html"
                                    }],

                                    DefaultCacheBehavior: {
                                        AllowedMethods: [
                                            "DELETE",
                                            "GET",
                                            "HEAD",
                                            "OPTIONS",
                                            "PATCH",
                                            "POST",
                                            "PUT"
                                        ],
                                        TargetOriginId: component.stackName,
                                        ForwardedValues: {
                                            QueryString: "'false'",
                                            Cookies: {
                                                Forward: "none"
                                            }
                                        },
                                        ViewerProtocolPolicy: "redirect-to-https"
                                    },
                                    ViewerCertificate: {
                                        AcmCertificateArn: "${self:provider.certArn}",
                                        SslSupportMethod: "sni-only",
                                    },
                                    Aliases: ["${self:provider.customDomainName}"]

                                }
                            }
                        },

                        DnsRecord: {
                            Type: "AWS::Route53::RecordSet",
                            Properties: {
                                AliasTarget: {
                                    DNSName: "!GetAtt WebAppCloudFrontDistribution.DomainName",
                                    HostedZoneId: "Z2FDTNDATAQYW2"
                                },
                                HostedZoneName: "${self:provider.hostedZoneName}.",
                                Name: "${self:provider.customDomainName}.",
                                Type: "'A'"
                            }
                        }
                    },
                    Outputs: {
                        WebAppCloudFrontDistributionOutput: {
                            Value: {
                                "Fn::GetAtt": "[ WebAppCloudFrontDistribution, DomainName ]"
                            }
                        }
                    }
                }

            } : {};

            return {
                stackType: "SOA",
                
                slsConfigs: deepmerge.all([
                    require("../../../infrastructure-scripts/dist/infra-comp-utils/sls-libs").toSoaSlsConfig(
                        component.stackName,
                        serverName,
                        component.buildPath,
                        component.assetsPath,
                        component.region,
                        dataLayerService.concat(component.services)
                    ),

                        // the datalayer (maybe a child-config) must load before the plugin serverless-offline!
                        ...childConfigs.map(config => config.slsConfigs),

                        // # allows running the stack locally on the dev-machine
                        {
                            plugins: ["serverless-offline", "serverless-pseudo-parameters"],

                            custom: {

                                "serverless-offline": {
                                    host: "0.0.0.0",
                                    port: "${self:provider.port, env:PORT, '3001'}"
                                }
                            }

                        },

                        domainConfig,



                        // add the IAM-Role-Statements
                        iamPermissions,

                        // assign the role
                        // assign the role
                        iamRoleAssignment
                    ]
                ),
                
                // add the server config 
                webpackConfigs: webpackConfigs.concat([soaWebPack, serverWebPack]),

                postBuilds: childConfigs.reduce((result, config) => result.concat(config.postBuilds),
                    [createHtml, writeDomainEnv, copyAssetsPostBuild, deployWithDomain, invalidateCloudFrontCache /*, postDeploy*/]),

                iamRoleStatements: [],
                
                environments: environments,

                stackName: component.stackName,

                assetsPath: undefined,

                buildPath: component.buildPath,

                region: component.region,

                domain: domain,

                certArn: certArn,

                supportOfflineStart: true,
                supportCreateDomain: true
            }
        }
    }

    return result;

};