package api

import (
	"fmt"
	"log"
	"net/http"

	"code.justin.tv/d8a/buddy/lib/config"
	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/systems/sandstorm/agent"

	"io/ioutil"

	"encoding/json"

	"time"

	"code.justin.tv/systems/sandstorm/manager"
	"golang.org/x/net/context"
)

var missingSecretsAndTimes = make(map[string]time.Time)

func (s *Server) SecretStatus(c context.Context, w http.ResponseWriter, r *http.Request) {
	resp := "OK"

	text, statusCode, err := readFrom("http://localhost:7331/")

	w.Header().Set("Content-Type", "text/plain;charset=utf-8")
	if err != nil {
		w.WriteHeader(500)
		writeText(w, err.Error())
		return
	}

	isSuccessCode := (statusCode / 100) == 2

	var sandstormHealth agent.HealthProfile
	err = json.Unmarshal([]byte(text), &sandstormHealth)
	if err != nil && isSuccessCode {
		w.WriteHeader(500)
	} else {
		w.WriteHeader(statusCode)
	}

	if err != nil {
		writeText(w, err.Error())
		return
	}

	if len(sandstormHealth.BlockedSecrets) > 0 {
		resp = fmt.Sprintln("One or more secrets are currently broken:")
		for _, secret := range sandstormHealth.BlockedSecrets {
			resp = fmt.Sprintf("%s\n - %s : %s", resp, secret.Secret, secret.ErrorMessage)
		}
	} else if !isSuccessCode {
		resp = fmt.Sprintf("Sandstorm reported an error: %s", sandstormHealth.RecentError)
	}

	retrievedSecrets := make(map[string]*manager.Secret)
	for _, secret := range sandstormHealth.SecretStatus {
		retrievedSecrets[secret.Name] = secret
	}

	missingSecretUsers := make(map[string]*config.User)

	var deadSecretUsers []*config.User
	for _, cluster := range s.config.Cluster {
		for _, user := range cluster.User {
			secret, ok := retrievedSecrets[user.Secret]

			if !ok {
				missingSecretUsers[user.Secret] = user
			} else if secret.Tombstone {
				//If it was deleted more than a minute ago, alert
				updatedAt := time.Unix(secret.UpdatedAt, 0)

				if time.Now().Add(-1 * time.Minute).After(updatedAt) {
					deadSecretUsers = append(deadSecretUsers, user)
				}
			}
		}
	}

	for secret := range missingSecretUsers {
		_, ok := missingSecretsAndTimes[secret]
		if !ok {
			missingSecretsAndTimes[secret] = time.Now()
		}
	}

	var outputMissingUsers []*config.User
	for secret, missingTime := range missingSecretsAndTimes {
		user, ok := missingSecretUsers[secret]
		if !ok {
			delete(missingSecretsAndTimes, secret)
		} else if time.Now().Add(-1 * time.Minute).After(missingTime) {
			outputMissingUsers = append(outputMissingUsers, user)
		}
	}

	//A secret is bad if one of the following is true-
	// - It has a tombstone & last update time is more than a minute ago
	// - Key has been missing entirely for over a minute
	if len(outputMissingUsers) > 0 || len(deadSecretUsers) > 0 {
		resp = ""
		w.WriteHeader(500)
		if len(outputMissingUsers) > 0 {
			resp = fmt.Sprintf("%sThe following buddy-managed secrets are missing from sandstorm agent:\n", resp)
			for _, user := range outputMissingUsers {
				resp = fmt.Sprintf("%s - %s (user %s)\n", resp, user.Secret, user.Name)
			}
		}
		if len(deadSecretUsers) > 0 {
			resp = fmt.Sprintf("%sThe following buddy-managed secrets are marked as deleted from sandstorm:\n", resp)
			for _, user := range deadSecretUsers {
				resp = fmt.Sprintf("%s - %s (user %s)\n", resp, user.Secret, user.Name)
			}
		}
	}

	writeText(w, resp)
}

func readFrom(url string) (string, int, error) {
	resp, err := http.Get("http://localhost:7331/")
	statusCode := 500
	if resp != nil {
		statusCode = resp.StatusCode
	}

	if err != nil || resp == nil {
		return "", statusCode, err
	}

	defer queries.TryClose(resp.Body)
	body, err := ioutil.ReadAll(resp.Body)
	return string(body), resp.StatusCode, err
}

func writeText(w http.ResponseWriter, text string) {
	_, err := w.Write([]byte(text))
	if err != nil {
		log.Println(w.Write([]byte(err.Error())))
	}
}
