package healthtiming

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"time"

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

func NewBackendTimingChecker(innerBackend common.BackendChecker, thresholdInMs time.Duration, relativeThreshold float64, statQuery StatQuery) common.BackendChecker {
	backend := &backendTimingChecker{
		innerBackend:      innerBackend,
		absoluteThreshold: time.Millisecond * thresholdInMs,
		relativeThreshold: relativeThreshold,
		statQuery:         statQuery,
		queryStatus:       nil,
		queryStatusChan:   make(chan common.Status),
	}

	go backend.makeQuery()

	return backend
}

type backendTimingChecker struct {
	innerBackend      common.BackendChecker
	absoluteThreshold time.Duration
	relativeThreshold float64
	statQuery         StatQuery
	queryStatus       common.Status
	queryStatusChan   chan common.Status
}

func (backend *backendTimingChecker) Check() (healthyMsg string, err error) {
	select {
	case backend.queryStatus = <-backend.queryStatusChan:
	default:
	}

	start := time.Now()
	healthy, err := backend.innerBackend.Check()
	duration := time.Now().Sub(start)

	if err == nil && duration > backend.absoluteThreshold && backend.queryStatus != nil && !backend.queryStatus.Ok() && time.Now().Sub(backend.queryStatus.At()) < 15*time.Second {
		return "", errors.New(backend.queryStatus.Message())
	}

	return healthy, err
}

func (backend *backendTimingChecker) Open() (*sql.DB, error) {
	return backend.innerBackend.Open()
}

func (backend *backendTimingChecker) makeQuery() {
	for {
		dataPoint, timestamp, err := backend.statQuery.Execute()
		if err != nil {
			log.Println(err)
			time.Sleep(10 * time.Second)
			continue
		}

		status := common.NewStatus(
			dataPoint < backend.relativeThreshold,
			fmt.Sprintf("The health query took longer than %v and was %f times an average of similar healthchecks from other machines.", backend.absoluteThreshold, dataPoint),
			timestamp,
		)

		select {
		case backend.queryStatusChan <- status:
		case _ = <-time.After(10 * time.Second):
		}
	}
}
