// Copyright 2018 ThousandEyes Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package environment

import (
	"fmt"
	"html/template"
	"io/ioutil"
	"net"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sync"

	"github.com/thousandeyes/shoelaces/internal/event"
	"github.com/thousandeyes/shoelaces/internal/log"
	"github.com/thousandeyes/shoelaces/internal/mappings"
	"github.com/thousandeyes/shoelaces/internal/server"
	"github.com/thousandeyes/shoelaces/internal/templates"
)

// Environment struct holds the shoelaces instance global data.
type Environment struct {
	ConfigFile      string
	HostnameMaps    []mappings.HostnameMap
	NetworkMaps     []mappings.NetworkMap
	ServerStates    *server.States
	EventLog        *event.Log
	ParamsBlacklist []string
	Templates       *templates.ShoelacesTemplates // Dynamic slc templates
	StaticTemplates *template.Template            // Static Templates
	Environments    []string                      // Valid config environments
	Logger          log.Logger

	BindAddr          string
	BaseURL           string
	DataDir           string
	StaticDir         string
	EnvDir            string
	TemplateExtension string
	MappingsFile      string
	Debug             bool
}

// New returns an initialized environment structure
func New() *Environment {
	env := defaultEnvironment()
	env.setFlags()
	env.validateFlags()

	if env.Debug {
		env.Logger = log.AllowDebug(env.Logger)
	}

	if env.BaseURL == "" {
		env.BaseURL = env.BindAddr
	}

	env.Environments = env.initEnvOverrides()

	env.EventLog = &event.Log{}

	env.Logger.Info("component", "environment", "msg", "Override found", "environment", env.Environments)

	mappingsPath := path.Join(env.DataDir, env.MappingsFile)
	if err := env.initMappings(mappingsPath); err != nil {
		panic(err)
	}

	env.initStaticTemplates()
	env.Templates.ParseTemplates(env.Logger, env.DataDir, env.EnvDir, env.Environments, env.TemplateExtension)
	server.StartStateCleaner(env.Logger, env.ServerStates)

	return env
}

func defaultEnvironment() *Environment {
	env := &Environment{}
	env.NetworkMaps = make([]mappings.NetworkMap, 0)
	env.HostnameMaps = make([]mappings.HostnameMap, 0)
	env.ServerStates = &server.States{sync.RWMutex{}, make(map[string]*server.State)}
	env.ParamsBlacklist = []string{"baseURL"}
	env.Templates = templates.New()
	env.Environments = make([]string, 0)
	env.Logger = log.MakeLogger(os.Stdout)

	return env
}

func (env *Environment) initStaticTemplates() {
	staticTemplates := []string{
		path.Join(env.StaticDir, "templates/html/header.html"),
		path.Join(env.StaticDir, "templates/html/index.html"),
		path.Join(env.StaticDir, "templates/html/events.html"),
		path.Join(env.StaticDir, "templates/html/mappings.html"),
		path.Join(env.StaticDir, "templates/html/footer.html"),
	}

	fmt.Println(env.StaticDir)

	for _, t := range staticTemplates {
		if _, err := os.Stat(t); err != nil {
			env.Logger.Error("component", "environment", "msg", "Template does not exists!", "environment", t)
			os.Exit(1)
		}
	}

	env.StaticTemplates = template.Must(template.ParseFiles(staticTemplates...))
}

func (env *Environment) initEnvOverrides() []string {
	var environments = make([]string, 0)
	envPath := filepath.Join(env.DataDir, env.EnvDir)
	files, err := ioutil.ReadDir(envPath)
	if err == nil {
		for _, f := range files {
			if f.IsDir() {
				environments = append(environments, f.Name())
			}
		}
	}
	return environments
}

func (env *Environment) initMappings(mappingsPath string) error {
	configMappings := mappings.ParseYamlMappings(env.Logger, mappingsPath)

	for _, configNetMap := range configMappings.NetworkMaps {
		_, ipnet, err := net.ParseCIDR(configNetMap.Network)
		if err != nil {
			return err
		}

		netMap := mappings.NetworkMap{Network: ipnet, Script: initScript(configNetMap.Script)}
		env.NetworkMaps = append(env.NetworkMaps, netMap)
	}

	for _, configHostMap := range configMappings.HostnameMaps {
		regex, err := regexp.Compile(configHostMap.Hostname)
		if err != nil {
			return err
		}

		hostMap := mappings.HostnameMap{Hostname: regex, Script: initScript(configHostMap.Script)}
		env.HostnameMaps = append(env.HostnameMaps, hostMap)
	}

	return nil
}

func initScript(configScript mappings.YamlScript) *mappings.Script {
	mappingScript := &mappings.Script{
		Name:        configScript.Name,
		Environment: configScript.Environment,
		Params:      make(map[string]interface{}),
	}
	for key := range configScript.Params {
		mappingScript.Params[key] = configScript.Params[key]
	}

	return mappingScript
}
