package config

import (
	"flag"
	"fmt"
	"net"
	"net/url"
	"path/filepath"
	"strings"

	"github.com/giongto35/cloud-game/v3/pkg/os"
)

type WorkerConfig struct {
	Encoder   Encoder
	Emulator  Emulator
	Library   Library
	Recording Recording
	Storage   Storage
	Worker    Worker
	Webrtc    Webrtc
	Version   Version
}

type Storage struct {
	Provider          string
	S3Endpoint        string
	S3BucketName      string
	S3AccessKeyId     string
	S3SecretAccessKey string
}

type Worker struct {
	Debug      bool
	Monitoring Monitoring
	Network    struct {
		CoordinatorAddress string
		Endpoint           string
		PingEndpoint       string
		PublicAddress      string
		Secure             bool
		Zone               string
	}
	Server Server
	Tag    string
}

type Encoder struct {
	Audio Audio
	Video Video
}

type Audio struct {
	Frames    []float32
	Resampler int
}

type Video struct {
	Codec   string
	Threads int
	H264    struct {
		Mode     string
		Crf      uint8
		MaxRate  int
		BufSize  int
		LogLevel int32
		Preset   string
		Profile  string
		Tune     string
	}
	Vpx struct {
		Bitrate          uint
		KeyframeInterval uint
	}
}

// allows custom config path
var workerConfigPath string

func NewWorkerConfig() (conf WorkerConfig, paths []string) {
	paths, err := LoadConfig(&conf, workerConfigPath)
	if err != nil {
		panic(err)
	}
	conf.expandSpecialTags()
	conf.fixValues()
	return
}

// ParseFlags updates config values from passed runtime flags.
// Define own flags with default value set to the current config param.
// Don't forget to call flag.Parse().
func (c *WorkerConfig) ParseFlags() {
	c.Worker.Server.WithFlags()
	flag.IntVar(&c.Worker.Monitoring.Port, "monitoring.port", c.Worker.Monitoring.Port, "Monitoring server port")
	flag.StringVar(&c.Worker.Network.CoordinatorAddress, "coordinatorhost", c.Worker.Network.CoordinatorAddress, "Worker URL to connect")
	flag.StringVar(&c.Worker.Network.Zone, "zone", c.Worker.Network.Zone, "Worker network zone (us, eu, etc.)")
	flag.StringVar(&workerConfigPath, "w-conf", workerConfigPath, "Set custom configuration file path")
	flag.Parse()
}

// expandSpecialTags replaces all the special tags in the config.
func (c *WorkerConfig) expandSpecialTags() {
	tag := "{user}"
	for _, dir := range []*string{&c.Emulator.Storage, &c.Emulator.Libretro.Cores.Repo.ExtLock} {
		if *dir == "" || !strings.Contains(*dir, tag) {
			continue
		}
		userHomeDir, err := os.GetUserHome()
		if err != nil {
			panic(fmt.Sprintf("couldn't read user home directory, %v", err))
		}
		*dir = strings.Replace(*dir, tag, userHomeDir, -1)
		*dir = filepath.FromSlash(*dir)
	}
}

// fixValues tries to fix some values otherwise hard to set externally.
func (c *WorkerConfig) fixValues() {
	// with ICE lite we clear ICE servers
	if c.Webrtc.IceLite {
		c.Webrtc.IceServers = []IceServer{}
	}
}

// GetAddr returns defined in the config server address.
func (w *Worker) GetAddr() string { return w.Server.GetAddr() }

// GetPingAddr returns exposed to clients server ping endpoint address.
func (w *Worker) GetPingAddr(address string) url.URL {
	_, srcPort, _ := net.SplitHostPort(w.GetAddr())
	dstHost, _, _ := net.SplitHostPort(address)
	address = net.JoinHostPort(dstHost, srcPort)

	if w.Network.PublicAddress != "" {
		address = w.Network.PublicAddress
		if w.Network.Zone != "" {
			address = w.Network.Zone + "." + address
		}
		port := srcPort
		if port != "" && port != "80" && port != "443" && port != "0" {
			address += ":" + port
		}
	}

	pingURL := url.URL{Scheme: "http", Host: address, Path: w.Network.PingEndpoint}
	if w.Server.Https {
		pingURL.Scheme = "https"
	}
	return pingURL
}

func (w *Worker) GetPort(address string) string {
	_, port, _ := net.SplitHostPort(address)
	return port
}
