package agent

import (
	"fmt"
	"slices"

	"git.adyxax.org/adyxax/spacetraders/golang/pkg/model"
)

type TradeGoodNotFoundError struct{}

func (err *TradeGoodNotFoundError) Error() string {
	return "trade good not found"
}

func (a *agent) buyTradeGood(ship *model.Ship, tradeGoodToBuy string) error {
	if ship.Cargo.Units == ship.Cargo.Capacity {
		return nil
	}
	// list markets would sell our goods
	markets, err := a.listMarketsInSystem(ship.Nav.SystemSymbol)
	if err != nil {
		return fmt.Errorf("failed to list markets in system %s: %w", ship.Nav.SystemSymbol, err)
	}
	markets = slices.DeleteFunc(markets, func(market model.Market) bool {
		for _, item := range market.Exports {
			if item.Symbol == tradeGoodToBuy {
				return false
			}
		}
		return true
	})
	if len(markets) == 0 {
		return &TradeGoodNotFoundError{}
	}
	// find the closest place to buy TODO
	waypoint, err := a.client.GetWaypoint(ship.Nav.WaypointSymbol, a.db)
	if err != nil {
		return fmt.Errorf("failed to get nav waypoint %s: %w", ship.Nav.WaypointSymbol, err)
	}
	waypoints := make([]model.Waypoint, 0)
	for i := range markets {
		waypoint, err := a.client.GetWaypoint(markets[i].Symbol, a.db)
		if err != nil {
			return fmt.Errorf("failed to get waypoint %s: %w", markets[i].Symbol, err)
		}
		waypoints = append(waypoints, *waypoint)
	}
	sortByDistanceFrom(waypoint, waypoints)
	// Go there and refresh our market data
	if err := a.client.Navigate(ship, waypoints[0].Symbol, a.db); err != nil {
		return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err)
	}
	market, err := a.client.GetMarket(waypoints[0].Symbol, a.db)
	if err != nil {
		return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err)
	}
	// Buy until full
	for _, tradeGood := range market.TradeGoods {
		if tradeGood.Type == "EXPORT" && tradeGood.Symbol == tradeGoodToBuy {
			for ship.Cargo.Units < ship.Cargo.Capacity {
				increment := min(ship.Cargo.Capacity-ship.Cargo.Units, tradeGood.TradeVolume)
				if err := a.client.Purchase(ship, tradeGoodToBuy, increment, a.db); err != nil {
					return fmt.Errorf("failed to purchase %d units of %s: %w", increment, tradeGoodToBuy, err)
				}
			}
			break
		}
	}
	return a.buyTradeGood(ship, tradeGoodToBuy)
}

func (a *agent) sellEverythingExcept(ship *model.Ship, keep string) error {
	// First lets see what we need to sell
	cargo := ship.Cargo.Inventory
	cargo = slices.DeleteFunc(cargo, func(inventory model.Inventory) bool {
		return inventory.Symbol == keep
	})
	if len(cargo) == 0 {
		return nil
	}
	// list markets would buy our goods
	markets, err := a.listMarketsInSystem(ship.Nav.SystemSymbol)
	if err != nil {
		return fmt.Errorf("failed to list markets in system %s: %w", ship.Nav.SystemSymbol, err)
	}
	markets = slices.DeleteFunc(markets, func(market model.Market) bool {
		for _, item := range market.Imports {
			for _, cargoItem := range cargo {
				if item.Symbol == cargoItem.Symbol {
					return false
				}
			}
		}
		return true
	})
	if len(markets) == 0 {
		return nil
	}
	// find the closest place to sell something TODO
	waypoint, err := a.client.GetWaypoint(ship.Nav.WaypointSymbol, a.db)
	if err != nil {
		return fmt.Errorf("failed to get nav waypoint %s: %w", ship.Nav.WaypointSymbol, err)
	}
	waypoints := make([]model.Waypoint, 0)
	for i := range markets {
		waypoint, err := a.client.GetWaypoint(markets[i].Symbol, a.db)
		if err != nil {
			return fmt.Errorf("failed to get waypoint %s: %w", markets[i].Symbol, err)
		}
		waypoints = append(waypoints, *waypoint)
	}
	sortByDistanceFrom(waypoint, waypoints)
	// Go there and refresh our market data
	if err := a.client.Navigate(ship, waypoints[0].Symbol, a.db); err != nil {
		return fmt.Errorf("failed to navigate to %s: %w", waypoints[0].Symbol, err)
	}
	market, err := a.client.GetMarket(waypoints[0].Symbol, a.db)
	if err != nil {
		return fmt.Errorf("failed to get market %s: %w", waypoints[0].Symbol, err)
	}
	// sell everything we can
	for _, cargoItem := range cargo {
		units := cargoItem.Units
		for _, tradeGood := range market.TradeGoods {
			if tradeGood.Type == "IMPORT" && tradeGood.Symbol == cargoItem.Symbol {
				for units > 0 {
					increment := min(units, tradeGood.TradeVolume)
					if err := a.client.Sell(ship, cargoItem.Symbol, increment, a.db); err != nil {
						return fmt.Errorf("failed to sell %d units of %s: %w", units, cargoItem.Symbol, err)
					}
					units = units - increment
				}
				break
			}
		}
	}
	return a.sellEverythingExcept(ship, keep)
}