// Copyright © 2016 Asteris, LLC
//
// 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 health

import (
	"bytes"
	"fmt"
	"text/template"

	"github.com/asteris-llc/converge/graph"
	pp "github.com/asteris-llc/converge/prettyprinters"
	"github.com/asteris-llc/converge/prettyprinters/human"
	"github.com/asteris-llc/converge/prettyprinters/tmpltools"
	"github.com/asteris-llc/converge/resource"
)

// Printer for health checks
type Printer struct {
	*human.Printer
	Summary bool
}

// healthWrapper wraps a HealthStatus with ID context
type healthWrapper struct {
	*resource.HealthStatus
	ID string
}

// New returns a new Printer with an embedded human printer that hides
// non-healthcheck nodes
func New() *Printer {
	return NewWithPrinter(human.New())
}

// NewWithPrinter uses the provided human printer
func NewWithPrinter(h *human.Printer) *Printer {
	return &Printer{Printer: h}
}

// FinishPP sumarizes the results of the health check
func (p *Printer) FinishPP(g *graph.Graph) (pp.Renderable, error) {
	var warnings int
	var errors int
	var deps int
	type summaryObj struct {
		Warnings int
		Errors   int
		Deps     int
	}
	root, err := g.Root()
	if err != nil {
		return pp.HiddenString(), err
	}
	for _, vertex := range g.Vertices() {
		if vertex == root {
			continue
		}

		meta, ok := g.Get(vertex)
		if !ok {
			continue
		}

		status, ok := meta.Value().(*resource.HealthStatus)
		if !ok {
			continue
		}
		if status.IsError() {
			errors++
		}
		if status.IsWarning() {
			warnings++
		}
		if len(status.FailingDeps) > 0 {
			deps++
		}
	}
	tmpl, err := p.template(`{{if (gt .Errors 0)}}{{red "Summary"}}{{else if (gt .Warnings 0)}}{{yellow "Summary"}}{{else}}Summary{{end}}: {{.Errors}} errors, {{.Warnings}} warnings
{{.Deps}} checks will fail due to failing dependencies
`)
	if err != nil {
		fmt.Println("failed to render template")
		return pp.HiddenString(), err
	}
	var out bytes.Buffer
	err = tmpl.Execute(&out, &summaryObj{Warnings: warnings, Errors: errors, Deps: deps})
	return &out, err
}

func (p *Printer) getDrawTemplate(st *resource.HealthStatus) (*template.Template, error) {
	if p.Summary {
		if !st.ShouldDisplay() {
			return p.template(``)
		}
		return p.template(`{{if .IsError}}{{red .ID}}{{else if .IsWarning}}{{yellow .ID}}{{else}}{{.ID}}{{end}}: Status: {{showWarning .WarningLevel}}; {{len .FailingDeps}} failing dependencies

`)
	}
	if !st.ShouldDisplay() {
		return p.template(`{{green .ID}}: OK

`)
	}
	return p.template(`{{if .IsError}}{{red .ID}}{{else if .IsWarning}}{{yellow .ID}}{{else}}{{.ID}}{{end}}: {{showWarning .WarningLevel}}
	Messages:
	{{- range $msg := .Messages}}
	{{indent $msg}}
	{{- end}}
	{{- if .HasChanges}}
	{{- range $key, $values := .Changes}}
	{{red $key}}: {{diff ($values.Original) ($values.Current)}}
	{{- end}}
	{{- end}}
	{{- if .HasFailingDeps}}
	Dependencies Have Failed:
	{{- range $dep, $val := .FailingDeps}}
	{{indent $dep}}: {{$val}}
	{{- end}}
	{{- end}}

`)
}

// DrawNode draws a single health check
func (p *Printer) DrawNode(g *graph.Graph, id string) (pp.Renderable, error) {
	type printerNode struct {
		ID string
		*resource.HealthStatus
	}

	if root, err := g.Root(); root == id || err != nil {
		return pp.HiddenString(), err
	}

	meta, ok := g.Get(id)
	if !ok {
		// not a node, deferring to the human printer
		return p.Printer.DrawNode(g, id)
	}

	healthStatus, ok := meta.Value().(*resource.HealthStatus)
	if !ok {
		// not a health node, deferring to the human printer
		return p.Printer.DrawNode(g, id)
	}

	tmpl, err := p.getDrawTemplate(healthStatus)

	if err != nil {
		return pp.HiddenString(), err
	}

	var out bytes.Buffer
	err = tmpl.Execute(&out, &printerNode{ID: id, HealthStatus: healthStatus})

	return &out, err
}

func (p *Printer) template(source string) (*template.Template, error) {
	return tmpltools.Run(p.Printer.Color, source)
}
