package historicaldata

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/openshift/origin/pkg/synthetictests/platformidentification"
)

// nextBestGuessers is the order in which to attempt to lookup other alternative matches that are close to this job type.
var nextBestGuessers = []NextBestKey{
	MicroReleaseUpgrade,
	MinorReleaseUpgrade,
	PreviousReleaseUpgrade,
	combine(PreviousReleaseUpgrade, MicroReleaseUpgrade),
	combine(PreviousReleaseUpgrade, MinorReleaseUpgrade),
}

// NextBestKey returns the next best key in the query_results.json generated from BigQuery and a bool indicating whether this guesser has an opinion.
// If the bool is false, the key should not be used.
// Returning true doesn't mean the key exists, it just means that the key is worth trying.
type NextBestKey func(in platformidentification.JobType) (platformidentification.JobType, bool)

// MinorReleaseUpgrade if we don't have data for the current fromRelease and it's a micro upgrade, perhaps we have data
// for a minor upgrade.  A 4.11 to 4.11 upgrade will attempt a 4.10 to 4.11 upgrade.
func MinorReleaseUpgrade(in platformidentification.JobType) (platformidentification.JobType, bool) {
	if len(in.FromRelease) == 0 {
		return platformidentification.JobType{}, false
	}

	fromReleaseMinor := getMinor(in.FromRelease)
	toReleaseMajor := getMajor(in.Release)
	toReleaseMinor := getMinor(in.Release)
	// if we're already a minor upgrade, this doesn't apply
	if fromReleaseMinor == (toReleaseMinor - 1) {
		return platformidentification.JobType{}, false
	}

	ret := platformidentification.CloneJobType(in)
	ret.FromRelease = fmt.Sprintf("%d.%d", toReleaseMajor, toReleaseMinor-1)
	return ret, true
}

// MicroReleaseUpgrade if we don't have data for the current fromRelease and it's a minor upgrade, perhaps we have data
// for a micro upgrade.  A 4.10 to 4.11 upgrade will attempt a 4.11 to 4.11 upgrade.
func MicroReleaseUpgrade(in platformidentification.JobType) (platformidentification.JobType, bool) {
	if len(in.FromRelease) == 0 {
		return platformidentification.JobType{}, false
	}

	fromReleaseMinor := getMinor(in.FromRelease)
	toReleaseMajor := getMajor(in.Release)
	toReleaseMinor := getMinor(in.Release)
	// if we're already a micro upgrade, this doesn't apply
	if fromReleaseMinor == toReleaseMinor {
		return platformidentification.JobType{}, false
	}

	ret := platformidentification.CloneJobType(in)
	ret.FromRelease = fmt.Sprintf("%d.%d", toReleaseMajor, toReleaseMinor)
	return ret, true
}

// PreviousReleaseUpgrade if we don't have data for the current toRelease, perhaps we have data for the congruent test
// on the prior release.   A 4.11 to 4.11 upgrade will attempt a 4.10 to 4.10 upgrade.  A 4.11 no upgrade, will attempt a 4.10 no upgrade.
func PreviousReleaseUpgrade(in platformidentification.JobType) (platformidentification.JobType, bool) {
	toReleaseMajor := getMajor(in.Release)
	toReleaseMinor := getMinor(in.Release)

	ret := platformidentification.CloneJobType(in)
	ret.Release = fmt.Sprintf("%d.%d", toReleaseMajor, toReleaseMinor-1)
	if len(in.FromRelease) > 0 {
		fromReleaseMinor := getMinor(in.FromRelease)
		ret.FromRelease = fmt.Sprintf("%d.%d", toReleaseMajor, fromReleaseMinor-1)
	}
	return ret, true
}

func getMajor(in string) int {
	major, err := strconv.ParseInt(strings.Split(in, ".")[0], 10, 32)
	if err != nil {
		panic(err)
	}
	return int(major)
}

func getMinor(in string) int {
	minor, err := strconv.ParseInt(strings.Split(in, ".")[1], 10, 32)
	if err != nil {
		panic(err)
	}
	return int(minor)
}

// OnOVN maybe we have data on OVN
func OnOVN(in platformidentification.JobType) (platformidentification.JobType, bool) {
	if in.Network == "ovn" {
		return platformidentification.JobType{}, false
	}

	ret := platformidentification.CloneJobType(in)
	ret.Network = "ovn"
	return ret, true
}

// OnSDN maybe we have data on SDN
func OnSDN(in platformidentification.JobType) (platformidentification.JobType, bool) {
	if in.Network == "sdn" {
		return platformidentification.JobType{}, false
	}

	ret := platformidentification.CloneJobType(in)
	ret.Network = "sdn"
	return ret, true
}

// combine will start with the input and call each guess in order.  It uses the output of the previous NextBestKeyFn
// as the input to the next.  This allows combinations like "previous release upgrade micro" without writing custom
// functions for each.
func combine(nextBestKeys ...NextBestKey) NextBestKey {
	return func(in platformidentification.JobType) (platformidentification.JobType, bool) {
		curr := in
		for _, nextBestKey := range nextBestKeys {
			var ok bool
			curr, ok = nextBestKey(curr)
			if !ok {
				return curr, false
			}
		}
		return curr, true
	}
}
