package result

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)

// Football evaluations

// Full Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the full 90 minutes of play.
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	switch outcome.OddName {
	case "1": // Home win
		if score.Home > score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Draw":
		if score.Home == score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "2": // Away win
		if score.Away > score.Home {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}

// Over/Under betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be over or under a specified number.
func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	totalGoals := float64(score.Home + score.Away)
	threshold, err := strconv.ParseFloat(outcome.OddName, 64)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}

	if outcome.OddHeader == "Over" {
		if totalGoals > threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if totalGoals == threshold {
			return domain.OUTCOME_STATUS_VOID, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	} else if outcome.OddHeader == "Under" {
		if totalGoals < threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if totalGoals == threshold {
			return domain.OUTCOME_STATUS_VOID, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	}
	return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
}

// Correct Score betting is a type of bet where the bettor predicts the exact final score of a match.
func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away)
	if outcome.OddName == expectedScore {
		return domain.OUTCOME_STATUS_WIN, nil
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

// Half Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the first half.
// This is the same as the full time result but only for the first half of the game
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	return evaluateFullTimeResult(outcome, score)
}

// This is a multiple outcome checker for the asian handicap and other kinds of bets
// The only outcome that are allowed are "Both Bets win", "Both Bets Lose", "Half Win and Half Void"
func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.OutcomeStatus) (domain.OutcomeStatus, error) {
	if secondOutcome == domain.OUTCOME_STATUS_PENDING {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("cannot check pending outcome")
	}

	if outcome == domain.OUTCOME_STATUS_ERROR || secondOutcome == domain.OUTCOME_STATUS_ERROR {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("❌ mutli outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
	}

	// fmt.Printf("| Multi Outcome | %v -> %v \n", outcome.String(), secondOutcome.String())

	switch outcome {
	case domain.OUTCOME_STATUS_PENDING:
		return secondOutcome, nil
	case domain.OUTCOME_STATUS_WIN:
		if secondOutcome == domain.OUTCOME_STATUS_WIN {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
			return domain.OUTCOME_STATUS_LOSS, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_HALF {
			return domain.OUTCOME_STATUS_VOID, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
			return domain.OUTCOME_STATUS_HALF, nil
		} else {
			fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
		}
	case domain.OUTCOME_STATUS_LOSS:
		if secondOutcome == domain.OUTCOME_STATUS_LOSS ||
			secondOutcome == domain.OUTCOME_STATUS_WIN ||
			secondOutcome == domain.OUTCOME_STATUS_HALF {
			return domain.OUTCOME_STATUS_LOSS, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
			return domain.OUTCOME_STATUS_VOID, nil
		} else {
			fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
		}
	case domain.OUTCOME_STATUS_VOID:
		if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS {
			return domain.OUTCOME_STATUS_VOID, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_VOID || secondOutcome == domain.OUTCOME_STATUS_HALF {
			return domain.OUTCOME_STATUS_VOID, nil
		} else {
			fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
		}
	case domain.OUTCOME_STATUS_HALF:
		if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_HALF {
			return domain.OUTCOME_STATUS_VOID, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
			return domain.OUTCOME_STATUS_LOSS, nil
		} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
			return domain.OUTCOME_STATUS_VOID, nil
		} else {
			fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
		}
	default:
		fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
	}
}

// Asian Handicap betting is a type of betting that eliminates the possibility of a draw by giving one team a virtual advantage or disadvantage.
// When the handicap has two values like "+0.5, +1.0" or "-0.5, -1.0", then it a multi outcome bet
// .
//
//	{
//		"id": "548319135",
//		"odds": "1.750",
//		"header": "1",
//		"handicap": "+0.5, +1.0"
//	},
//
//	{
//		"id": "548319139",
//		"odds": "1.950",
//		"header": "2",
//		"handicap": "-0.5, -1.0"
//	}
func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	handicapList := strings.Split(outcome.OddHandicap, ",")
	newOutcome := domain.OUTCOME_STATUS_PENDING
	for _, handicapStr := range handicapList {
		handicapStr = strings.TrimSpace(handicapStr)
		handicap, err := strconv.ParseFloat(handicapStr, 64)
		if err != nil {
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
		}
		adjustedHomeScore := float64(score.Home)
		adjustedAwayScore := float64(score.Away)
		if outcome.OddHeader == "1" { // Home team
			adjustedHomeScore += handicap
		} else if outcome.OddHeader == "2" { // Away team
			adjustedAwayScore += handicap
		} else {
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
		}

		if adjustedHomeScore > adjustedAwayScore {
			if outcome.OddHeader == "1" {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
				if err != nil {
					return domain.OUTCOME_STATUS_ERROR, err
				}
			} else {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
				if err != nil {
					fmt.Printf("multi outcome check error")
					return domain.OUTCOME_STATUS_PENDING, err
				}
			}
		} else if adjustedHomeScore < adjustedAwayScore {
			if outcome.OddHeader == "2" {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
				if err != nil {
					return domain.OUTCOME_STATUS_ERROR, err
				}
			} else {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
				if err != nil {
					fmt.Printf("multi outcome check error")
					return domain.OUTCOME_STATUS_PENDING, err
				}
			}
		}
		if newOutcome == domain.OUTCOME_STATUS_PENDING {
			newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
			if err != nil {
				fmt.Printf("multi outcome check error")
				return domain.OUTCOME_STATUS_PENDING, err
			}
		}
	}
	return newOutcome, nil
}

// Goal Line betting, also known as Over/Under betting,
// involves predicting the total number of goals scored in a match, regardless of which team wins.
//
//	{
//		"id": "548319141",
//		"odds": "1.800",
//		"header": "Over",
//		"name": "1.5, 2.0"
//	},
//
//	{
//		"id": "548319146",
//		"odds": "1.900",
//		"header": "Under",
//		"name": "1.5, 2.0"
//	}
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	totalGoals := float64(score.Home + score.Away)
	thresholdList := strings.Split(outcome.OddName, ",")

	newOutcome := domain.OUTCOME_STATUS_PENDING
	for _, thresholdStr := range thresholdList {
		thresholdStr = strings.TrimSpace(thresholdStr)
		threshold, err := strconv.ParseFloat(thresholdStr, 64)
		if err != nil {
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: '%s', %v", thresholdStr, err)
		}

		oddHeader := strings.TrimSpace(outcome.OddHeader)
		if oddHeader == "Over" {
			if totalGoals > threshold {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
				if err != nil {
					return domain.OUTCOME_STATUS_ERROR, err
				}
			} else if totalGoals == threshold {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
				if err != nil {

					return domain.OUTCOME_STATUS_ERROR, err
				}
			}
			newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
			if err != nil {
				return domain.OUTCOME_STATUS_ERROR, err
			}
		} else if oddHeader == "Under" {
			if totalGoals < threshold {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
				if err != nil {

					return domain.OUTCOME_STATUS_ERROR, err
				}
			} else if totalGoals == threshold {
				newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
				if err != nil {

					return domain.OUTCOME_STATUS_ERROR, err
				}
			}
			newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
			if err != nil {
				return domain.OUTCOME_STATUS_ERROR, err
			}

		} else {
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: '%s'", oddHeader)
		}

	}

	return newOutcome, nil
}

// First Team To Score betting is a type of bet where the bettor predicts which team will score first in a match.
// We can get this from the "events" field on the result json
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
	for _, event := range events {
		if strings.Contains(event["text"], "1st Goal") || strings.Contains(event["text"], "Goal 1") {
			if strings.Contains(event["text"], outcome.HomeTeamName) && outcome.OddName == "1" {
				return domain.OUTCOME_STATUS_WIN, nil
			} else if strings.Contains(event["text"], outcome.AwayTeamName) && outcome.OddName == "2" {
				return domain.OUTCOME_STATUS_WIN, nil
			}
			return domain.OUTCOME_STATUS_LOSS, nil
		}
	}
	return domain.OUTCOME_STATUS_VOID, nil // No goals scored
}

// Goals Odd/Even betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be odd or even.
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	totalGoals := score.Home + score.Away
	isOdd := totalGoals%2 == 1
	if outcome.OddName == "Odd" && isOdd {
		return domain.OUTCOME_STATUS_WIN, nil
	} else if outcome.OddName == "Even" && !isOdd {
		return domain.OUTCOME_STATUS_WIN, nil
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

func evaluateTeamOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	switch outcome.OddHeader {
	case "1":
		if outcome.OddHandicap == "Odd" {
			if score.Home%2 == 1 {
				return domain.OUTCOME_STATUS_WIN, nil
			}
			return domain.OUTCOME_STATUS_LOSS, nil
		} else if outcome.OddHandicap == "Even" {
			if score.Home%2 == 0 {
				return domain.OUTCOME_STATUS_WIN, nil
			}
			return domain.OUTCOME_STATUS_LOSS, nil
		} else {
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd handicap: %s", outcome.OddHandicap)
		}
	case "2":
		if outcome.OddHandicap == "Odd" {
			if score.Away%2 == 1 {
				return domain.OUTCOME_STATUS_WIN, nil
			}
			return domain.OUTCOME_STATUS_LOSS, nil
		} else if outcome.OddHandicap == "Even" {
			if score.Away%2 == 0 {
				return domain.OUTCOME_STATUS_WIN, nil
			}
			return domain.OUTCOME_STATUS_LOSS, nil
		} else {
			return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd handicap: %s", outcome.OddHandicap)
		}
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)

	}
}

// Double Chance betting is a type of bet where the bettor predicts two of the three possible outcomes of a match.
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	isHomeWin := score.Home > score.Away
	isDraw := score.Home == score.Away
	isAwayWin := score.Away > score.Home
	switch outcome.OddName {
	case "1 or Draw", (outcome.HomeTeamName + " or " + "Draw"), ("Draw" + " or " + outcome.HomeTeamName):
		if isHomeWin || isDraw {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Draw or 2", ("Draw" + " or " + outcome.AwayTeamName), (outcome.AwayTeamName + " or " + "Draw"):
		if isDraw || isAwayWin {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "1 or 2", (outcome.HomeTeamName + " or " + outcome.AwayTeamName), (outcome.AwayTeamName + " or " + outcome.HomeTeamName):
		if isHomeWin || isAwayWin {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}

// Draw No Bet betting is a type of bet where the bettor predicts the outcome of a match, but if the match ends in a draw, the bet is voided.
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	if score.Home == score.Away {
		return domain.OUTCOME_STATUS_VOID, nil
	}
	if outcome.OddName == "1" && score.Home > score.Away {
		return domain.OUTCOME_STATUS_WIN, nil
	} else if outcome.OddName == "2" && score.Away > score.Home {
		return domain.OUTCOME_STATUS_WIN, nil
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

func evaluateCorners(outcome domain.BetOutcome, corners struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	totalCorners := corners.Home + corners.Away
	threshold, err := strconv.ParseFloat(outcome.OddName, 10)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}
	switch outcome.OddHeader {
	case "Over":
		if totalCorners > int(threshold) {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Under":
		if totalCorners < int(threshold) {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Exactly":
		if totalCorners == int(threshold) {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
	}
}

// Basketball evaluations

// Game Lines is an aggregate of money line, spread and total betting markets in one
func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	switch outcome.OddName {
	case "Money Line":
		return evaluateMoneyLine(outcome, score)

	case "Spread", "Line", "Run Line":
		// Since Spread betting is essentially the same thing
		return evaluateAsianHandicap(outcome, score)
	case "Total":
		return evaluateTotalOverUnder(outcome, score)
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}

// Money Line betting is a type of bet where the bettor predicts the outcome of a match without any point spread.
func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	switch outcome.OddHeader {
	case "1":
		if score.Home > score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil

	case "2":
		if score.Home < score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Tie", "Draw":
		if score.Home == score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}

// Total Over/Under betting is a type of bet where the bettor predicts whether the total number of points scored in a match will be over or under a specified number.
func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	// The handicap will be in the format "U {float}" or "O {float}"
	// U and O denoting over and under for this case
	overUnderStr := strings.Split(outcome.OddHandicap, " ")
	if len(overUnderStr) != 2 {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}
	threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}

	// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
	totalScore := float64(score.Home + score.Away)

	if overUnderStr[0] == "O" {
		if totalScore > threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	} else if overUnderStr[0] == "U" {
		if totalScore < threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	} else if overUnderStr[0] == "E" {
		if totalScore == threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	}
	return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
}

func evaluateTotalLegs(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	total, err := strconv.ParseFloat(outcome.OddName, 64)
	if err != nil {
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid : %s", outcome.OddName)
	}

	totalLegs := float64(score.Home + score.Away)

	switch outcome.OddHeader {
	case "Over":
		if totalLegs > total {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Under":
		if totalLegs < total {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	}

	return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
}

// Result and Total betting is a type of bet where the bettor predicts
// the outcome of a match and whether the total number of points scored will be over or under a specified number.
func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	// The handicap will be in the format "U {float}" or "O {float}"
	// U and O denoting over and under for this case
	overUnderStr := strings.Split(outcome.OddHandicap, " ")
	if len(overUnderStr) != 2 {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}

	overUnder := overUnderStr[0]

	if overUnder != "Over" && overUnder != "Under" {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
	}
	threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}

	// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
	totalScore := float64(score.Home + score.Away)

	switch outcome.OddHeader {
	case "1":
		if score.Home < score.Away {
			return domain.OUTCOME_STATUS_LOSS, nil
		}

		if overUnder == "Over" && totalScore > threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if overUnder == "Under" && totalScore < threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "2":
		if score.Away < score.Home {
			return domain.OUTCOME_STATUS_LOSS, nil
		}
		if overUnder == "Over" && totalScore > threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if overUnder == "Under" && totalScore < threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}

		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
	}
}

// Team Total betting is a type of bet where the bettor predicts the total number of points scored by a specific team in a match
// is over or under a specified number.
func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	// The handicap will be in the format "U {float}" or "O {float}"
	// U and O denoting over and under for this case
	overUnderStr := strings.Split(outcome.OddHandicap, " ")
	if len(overUnderStr) != 2 {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
	}

	overUnder := overUnderStr[0]

	if overUnder != "Over" && overUnder != "Under" {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
	}
	threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
	}

	// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
	HomeScore := float64(score.Home)
	AwayScore := float64(score.Away)

	switch outcome.OddHeader {
	case "1":
		if overUnder == "Over" && HomeScore > threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if overUnder == "Under" && HomeScore < threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "2":
		if overUnder == "Over" && AwayScore > threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if overUnder == "Under" && AwayScore < threshold {
			return domain.OUTCOME_STATUS_WIN, nil
		}

		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
	}
}

// Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points
// and also the result fo the match
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	// The name parameter will hold value "name": "{team_name} and {Yes | No}"
	// The best way to do this is to evaluate backwards since there might be
	// teams with 'and' in their name
	// We know that there is going to be "Yes" and "No "
	oddNameSplit := strings.Split(outcome.OddName, " ")

	scoreCheckSplit := oddNameSplit[len(oddNameSplit)-1]
	var isScorePoints bool
	if scoreCheckSplit == "Yes" {
		isScorePoints = true
	} else if scoreCheckSplit == "No" {
		isScorePoints = false
	} else {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}

	teamName := strings.TrimSpace(strings.Join(oddNameSplit[:len(oddNameSplit)-2], ""))

	threshold, err := strconv.ParseInt(outcome.OddHeader, 10, 64)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHeader)
	}

	// above code removes any space from team name, so do the same for outcome.HomeTeamName and outcome.AwayTeamName
	outcome.HomeTeamName = strings.Join(strings.Split(outcome.HomeTeamName, " "), "")
	outcome.AwayTeamName = strings.Join(strings.Split(outcome.AwayTeamName, " "), "")

	switch teamName {
	case outcome.HomeTeamName:
		if score.Home > score.Away {
			if isScorePoints && score.Home >= int(threshold) && score.Away >= int(threshold) {
				return domain.OUTCOME_STATUS_WIN, nil
			} else if !isScorePoints && score.Home < int(threshold) && score.Away < int(threshold) {
				return domain.OUTCOME_STATUS_WIN, nil
			}
		}
	case outcome.AwayTeamName:
		if score.Away > score.Home {
			if isScorePoints && score.Home >= int(threshold) && score.Away >= int(threshold) {
				return domain.OUTCOME_STATUS_WIN, nil
			} else if !isScorePoints && score.Home < int(threshold) && score.Away < int(threshold) {
				return domain.OUTCOME_STATUS_WIN, nil
			}
		}
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("team name error: %s", teamName)
	}

	return domain.OUTCOME_STATUS_LOSS, nil

}

// Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points.
func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	threshold, err := strconv.ParseInt(outcome.OddName, 10, 64)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
	}

	switch outcome.OddHeader {
	case "Yes":
		if score.Home >= int(threshold) && score.Away >= int(threshold) {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "No":
		if score.Home < int(threshold) && score.Away < int(threshold) {
			return domain.OUTCOME_STATUS_WIN, nil
		}

	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
	}

	return domain.OUTCOME_STATUS_LOSS, nil
}

// Money Line 3 Way betting is a type of bet where the bettor predicts the outcome of a match with three possible outcomes: home win, away win, or draw.
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	switch outcome.OddName {
	case "1": // Home win
		if score.Home > score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "Tie":
		if score.Home == score.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "2": // Away win
		if score.Away > score.Home {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}

func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, fullTimeScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	halfWins := strings.Split(outcome.OddName, "-")
	if len(halfWins) != 2 {
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
	firstHalfWinner := strings.TrimSpace(halfWins[0])
	fullTimeWinner := strings.TrimSpace(halfWins[1])

	if firstHalfWinner != outcome.HomeTeamName && firstHalfWinner != outcome.AwayTeamName && firstHalfWinner != "Tie" {
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
	}
	if fullTimeWinner != outcome.HomeTeamName && fullTimeWinner != outcome.AwayTeamName && fullTimeWinner != "Tie" {
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
	}

	switch {
	case firstHalfWinner == outcome.HomeTeamName && firstHalfScore.Home < firstHalfScore.Away:
		return domain.OUTCOME_STATUS_LOSS, nil
	case firstHalfWinner == outcome.AwayTeamName && firstHalfScore.Away < firstHalfScore.Home:
		return domain.OUTCOME_STATUS_LOSS, nil
	case firstHalfWinner == "Tie" && firstHalfScore.Home != firstHalfScore.Away:
		return domain.OUTCOME_STATUS_LOSS, nil
	}

	switch {
	case fullTimeWinner == outcome.HomeTeamName && fullTimeScore.Home < fullTimeScore.Away:
		return domain.OUTCOME_STATUS_LOSS, nil
	case fullTimeWinner == outcome.AwayTeamName && fullTimeScore.Away < fullTimeScore.Home:
		return domain.OUTCOME_STATUS_LOSS, nil
	case fullTimeWinner == "Tie" && fullTimeScore.Home != fullTimeScore.Away:
		return domain.OUTCOME_STATUS_LOSS, nil
	}

	return domain.OUTCOME_STATUS_WIN, nil
}

// Highest Scoring Half betting is a type of bet where the bettor predicts which half of the match will have the highest total score.
func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	firstHalfTotal := firstScore.Home + firstScore.Away
	secondHalfTotal := secondScore.Home + secondScore.Away
	switch outcome.OddName {
	case "1st Half":
		if firstHalfTotal > secondHalfTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "2nd Half":
		if firstHalfTotal < secondHalfTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "Tie":
		if firstHalfTotal == secondHalfTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

// Highest Scoring Quarter betting is a type of bet where the bettor predicts which quarter of the match will have the highest score.
func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	firstQuarterTotal := firstScore.Home + firstScore.Away
	secondQuarterTotal := secondScore.Home + secondScore.Away
	thirdQuarterTotal := thirdScore.Home + thirdScore.Away
	fourthQuarterTotal := fourthScore.Home + fourthScore.Away

	switch outcome.OddName {
	case "1st Quarter":
		if firstQuarterTotal > secondQuarterTotal && firstQuarterTotal > thirdQuarterTotal && firstQuarterTotal > fourthQuarterTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "2nd Quarter":
		if secondQuarterTotal > firstQuarterTotal && secondQuarterTotal > thirdQuarterTotal && secondQuarterTotal > fourthQuarterTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "3rd Quarter":
		if thirdQuarterTotal > firstQuarterTotal && thirdQuarterTotal > secondQuarterTotal && thirdQuarterTotal > fourthQuarterTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "4th Quarter":
		if fourthQuarterTotal > firstQuarterTotal && fourthQuarterTotal > secondQuarterTotal && fourthQuarterTotal > thirdQuarterTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "Tie":
		if firstQuarterTotal == secondQuarterTotal || secondQuarterTotal == thirdQuarterTotal || thirdQuarterTotal == fourthQuarterTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

// Team With Highest Scoring Quarter betting is a type of bet where the bettor predicts which team will have the highest score in a specific quarter.
func evaluateTeamWithHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	homeTeamHighestQuarter := max(firstScore.Home, secondScore.Home, thirdScore.Home, fourthScore.Home)
	awayTeamHighestQuarter := max(firstScore.Away, secondScore.Away, thirdScore.Away, fourthScore.Away)

	switch outcome.OddName {
	case "1":
		if homeTeamHighestQuarter > awayTeamHighestQuarter {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "2":
		if awayTeamHighestQuarter > homeTeamHighestQuarter {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "Tie":
		if homeTeamHighestQuarter == awayTeamHighestQuarter {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

// Handicap and Total betting is a combination of spread betting and total points betting
// where the bettor predicts the outcome of a match with a point spread and the total number of points scored is over or under a specified number.
func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {

	nameSplit := strings.Split(outcome.OddName, " ")
	// Evaluate from bottom to get the threshold and find out if its over or under
	threshold, err := strconv.ParseFloat(nameSplit[len(nameSplit)-1], 10)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing threshold: %s", outcome.OddName)
	}
	total := float64(score.Home + score.Away)
	overUnder := nameSplit[len(nameSplit)-2]
	if overUnder == "Over" {
		if total < threshold {
			return domain.OUTCOME_STATUS_LOSS, nil
		}
	} else if overUnder == "Under" {
		if total > threshold {
			return domain.OUTCOME_STATUS_LOSS, nil
		}
	} else {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over and under: %s", outcome.OddName)
	}

	handicap, err := strconv.ParseFloat(nameSplit[len(nameSplit)-4], 10)
	if err != nil {
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing handicap: %s", outcome.OddName)
	}

	teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], ""))
	adjustedHomeScore := float64(score.Home)
	adjustedAwayScore := float64(score.Away)

	outcome.HomeTeamName = strings.Join(strings.Split(outcome.HomeTeamName, " "), "")
	outcome.AwayTeamName = strings.Join(strings.Split(outcome.AwayTeamName, " "), "")

	switch teamName {
	case outcome.HomeTeamName:
		adjustedHomeScore += handicap
		if adjustedHomeScore > adjustedAwayScore {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case outcome.AwayTeamName:
		adjustedAwayScore += handicap
		if adjustedAwayScore > adjustedHomeScore {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
	}

}

func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	if len(outcome.OddName) < 1 {
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
	}

	isGtr := false
	idx := len(outcome.OddName)
	if outcome.OddName[idx-1] == '+' {
		isGtr = true
		idx--
	}

	margin, err := strconv.ParseInt(outcome.OddName[:idx], 10, 64)
	if err != nil {
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
	}

	switch outcome.OddHeader {
	case "1":
		if score.Home == (score.Away + int(margin)) {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if isGtr && score.Home > (score.Away+int(margin)) {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "2":
		if score.Away == (score.Home + int(margin)) {
			return domain.OUTCOME_STATUS_WIN, nil
		} else if isGtr && score.Away > (score.Home+int(margin)) {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	}

	return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
}

// Highest Scoring Period betting is a type of bet where the bettor predicts which period of the match will have the highest total score.
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	firstPeriodTotal := firstScore.Home + firstScore.Away
	secondPeriodTotal := secondScore.Home + secondScore.Away
	thirdPeriodTotal := thirdScore.Home + thirdScore.Away

	switch outcome.OddName {
	case "Period 1":
		if firstPeriodTotal > secondPeriodTotal && firstPeriodTotal > thirdPeriodTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "Period 2":
		if secondPeriodTotal > firstPeriodTotal && secondPeriodTotal > thirdPeriodTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "Period 3":
		if thirdPeriodTotal > firstPeriodTotal && thirdPeriodTotal > secondPeriodTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	case "Tie":
		if firstPeriodTotal == secondPeriodTotal || secondPeriodTotal == thirdPeriodTotal {
			return domain.OUTCOME_STATUS_WIN, nil
		}
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
	}
	return domain.OUTCOME_STATUS_LOSS, nil
}

// Tied After Regulation is a type of bet where the bettor predicts whether the match will end in a tie after regulation time.
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	totalScore := struct{ Home, Away int }{0, 0}
	for _, score := range scores {
		totalScore.Home += score.Home
		totalScore.Away += score.Away
	}
	switch outcome.OddName {
	case "Yes":
		if totalScore.Home == totalScore.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	case "No":
		if totalScore.Home != totalScore.Away {
			return domain.OUTCOME_STATUS_WIN, nil
		}
		return domain.OUTCOME_STATUS_LOSS, nil
	}

	return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}

func evaluateVolleyballGamelines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	switch outcome.OddName {
	case "Total":
		return evaluateTotalOverUnder(outcome, score)
	default:
		return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}

func evaluateGameBettingTwoWay(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
	switch outcome.OddName {
	case "Handicap":
		return evaluateAsianHandicap(outcome, score)
	case "Total":
		return evaluateTotalOverUnder(outcome, score)
	case "To Win":
		return evaluateFullTimeResult(outcome, score)
	default:
		return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
	}
}
