package forcedstatus

import (
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"code.justin.tv/d8a/pg-healthcheck/common"
)

func NewForcedStatusChecker(innerBackendChecker common.BackendChecker, statusWatcher StatusWatcher, port int) common.BackendChecker {
	checker := &forcedStatusChecker{
		innerBackendChecker: innerBackendChecker,
		statusWatcher:       statusWatcher,
		port:                strconv.Itoa(port),
		asyncSignal:         make(chan bool),
	}
	go checker.processAsyncChecker()

	//This is a dumb hack- in some environments, the goroutine seems to wait
	//until the main thread chills for a second to actually start up.  This is probably fine
	//in prod but does annoying things in unit tests so I kick it over immediately & add an extra
	//channel wait to the goroutine before it starts actually processing data
	checker.asyncSignal <- true
	return checker
}

type forcedStatusChecker struct {
	innerBackendChecker common.BackendChecker
	statusWatcher       StatusWatcher
	port                string
	asyncSignal         chan bool
}

func (checker *forcedStatusChecker) Check() (string, error) {
	var currentStatus = checker.statusWatcher.GetStatus(checker.port)

	if currentStatus == nil {
		return checker.innerBackendChecker.Check()
	}

	//If the asyncSignal channel doesn't immediately accept the signal,
	//that means it's still running the check from a previous signal.  In this case,
	//we can just move on and not worry about signalling on this check
	select {
	case checker.asyncSignal <- true:
	default:
	}

	healthy := *currentStatus

	if healthy.healthy {
		return fmt.Sprintf("Administrator marked healthy: %s", strings.TrimSpace(healthy.healthReason)), nil
	}

	return "", fmt.Errorf("Administrator marked unhealthy: %s", strings.TrimSpace(healthy.healthReason))
}

func (checker *forcedStatusChecker) Open() (*sql.DB, error) {
	return checker.innerBackendChecker.Open()
}

//Inner status checkers may do things like logging errors & statting metrics that we want to have happen
//However, they can also do things like take 30s if the db is dead, causing a forced-healthy status to
//display unhealthy because it's stale.  So if a forced status is active, we return it immediately and
//set up an async process to run the inner check if one isn't already running
func (checker *forcedStatusChecker) processAsyncChecker() {
	_ = <-checker.asyncSignal
	for {
		_ = <-checker.asyncSignal

		//We ignore the error here because if we want to log this stuff, we can do it with a logchecker
		_, _ = checker.innerBackendChecker.Check()
	}
}
