package status

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
)

type InstanceMonitor struct {
	m         Monitor
	instances map[string]ResourceOuts
}

func NewInstanceMonitor(m Monitor) *InstanceMonitor {
	return &InstanceMonitor{
		m:         m,
		instances: map[string]ResourceOuts{},
	}
}

func (m *InstanceMonitor) Monitor(id string, resources ResourceOuts) {
	m.instances[id] = resources
}

func (m *InstanceMonitor) Get(id string) (map[DodoResource]Status, error) {
	ret := map[DodoResource]Status{}
	namespace := m.instances[id].Release.Namespace
	for _, out := range m.instances[id].Outs {
		if _, err := m.monitor(namespace, DodoResource{"internal", out.Name}, out, ret); err != nil {
			return nil, err
		}
	}
	for i := range ret {
		if i.Type == "internal" {
			delete(ret, i)
		}
	}
	return ret, nil
}

func mergeStatus(a Status, b Status) Status {
	if a > b {
		return a
	} else {
		return b
	}
}

// TODO(gio): handle volume
func (m *InstanceMonitor) monitor(namespace string, resource DodoResource, out ResourceOut, ret map[DodoResource]Status) (Status, error) {
	status := StatusNoStatus
	if resource.Type == "ingress" {
		var err error
		status, err = m.m.Get(Resource{
			Type: ResourceIngress,
			ResourceRef: ResourceRef{
				Id:        out.Id,
				Name:      fmt.Sprintf("ingress-%s", strings.TrimPrefix(resource.Name, "https://")),
				Namespace: namespace,
			},
		})
		if err != nil {
			return StatusNoStatus, err
		}
	} else {
		for _, h := range out.Helm {
			hs, err := m.m.Get(Resource{
				Type: ResourceHelmRelease,
				ResourceRef: ResourceRef{
					Id:        h.Id,
					Name:      h.Name,
					Namespace: namespace,
				},
			})
			if err != nil {
				return StatusNoStatus, err
			}
			status = mergeStatus(status, hs)
		}
	}
	for _, i := range out.Volume {
		if s, err := m.monitor(namespace, DodoResource{"volume", i.Name}, i, ret); err != nil {
			return StatusNoStatus, err
		} else {
			status = mergeStatus(status, s)
		}
	}
	for _, i := range out.PostgreSQL {
		if s, err := m.monitor(namespace, DodoResource{"postgresql", i.Name}, i, ret); err != nil {
			return StatusNoStatus, err
		} else {
			status = mergeStatus(status, s)
		}
	}
	for _, i := range out.MongoDB {
		if s, err := m.monitor(namespace, DodoResource{"mongodb", i.Name}, i, ret); err != nil {
			return StatusNoStatus, err
		} else {
			status = mergeStatus(status, s)
		}
	}
	for _, i := range out.Ingress {
		name := fmt.Sprintf("https://%s.%s", i.Subdomain, i.Network.Domain)
		if s, err := m.monitor(namespace, DodoResource{"ingress", name}, i.ResourceOut, ret); err != nil {
			return StatusNoStatus, err
		} else {
			status = mergeStatus(status, s)
		}
	}
	for _, i := range out.Services {
		if s, err := m.monitor(namespace, DodoResource{"service", i.Name}, i, ret); err != nil {
			return StatusNoStatus, err
		} else {
			status = mergeStatus(status, s)
		}
	}
	ret[resource] = status
	return status, nil
}

type resourceIngress struct {
	ResourceOut
	Network struct {
		Domain string `json:"domain"`
	} `json:"network"`
	Subdomain string `json:"subdomain"`
}

type resourceHelm struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type ResourceOut struct {
	Id         string                     `json:"id"`
	Name       string                     `json:"name"`
	Volume     map[string]ResourceOut     `json:"volume"`
	PostgreSQL map[string]ResourceOut     `json:"postgresql"`
	MongoDB    map[string]ResourceOut     `json:"mongodb"`
	Ingress    map[string]resourceIngress `json:"ingress"`
	Services   map[string]ResourceOut     `json:"services"`
	Helm       map[string]resourceHelm    `json:"helmR"`
}

type ResourceOuts struct {
	Release struct {
		Namespace string `json:"namespace"`
	}
	Outs map[string]ResourceOut `json:"outs"`
}

type DodoResource struct {
	Type string
	Name string
}

type DodoResourceStatus struct {
	Type   string `json:"type"`
	Name   string `json:"name"`
	Status string `json:"status"`
}

func DecodeResourceOuts(raw []byte) (ResourceOuts, error) {
	var outs ResourceOuts
	if err := json.NewDecoder(bytes.NewReader(raw)).Decode(&outs); err != nil {
		return ResourceOuts{}, err
	}
	return outs, nil
}
