package cmd

import (
	"fmt"
	"io"
	"net/http"
	"os"

	"github.com/apernet/hysteria/extras/utils"
	"github.com/mdp/qrterminal/v3"
	"github.com/oschwald/geoip2-golang"
)

const (
	geoipDefaultFilename = "GeoLite2-Country.mmdb"
	geoipDownloadURL     = "https://git.io/GeoLite2-Country.mmdb"
)

// convBandwidth handles both string and int types for bandwidth.
// When using string, it will be parsed as a bandwidth string with units.
// When using int, it will be parsed as a raw bandwidth in bytes per second.
// It does NOT support float types.
func convBandwidth(bw interface{}) (uint64, error) {
	switch bwT := bw.(type) {
	case string:
		return utils.StringToBps(bwT)
	case int:
		return uint64(bwT), nil
	default:
		return 0, fmt.Errorf("invalid type %T for bandwidth", bwT)
	}
}

func printQR(str string) {
	qrterminal.GenerateWithConfig(str, qrterminal.Config{
		Level:     qrterminal.L,
		Writer:    os.Stdout,
		BlackChar: qrterminal.BLACK,
		WhiteChar: qrterminal.WHITE,
	})
}

type configError struct {
	Field string
	Err   error
}

func (e configError) Error() string {
	return fmt.Sprintf("invalid config: %s: %s", e.Field, e.Err)
}

func (e configError) Unwrap() error {
	return e.Err
}

// geoipLoader provides the on-demand GeoIP database loading function required by the ACL engine.
type geoipLoader struct {
	Filename        string
	DownloadFunc    func(filename, url string) // Called when downloading the GeoIP database.
	DownloadErrFunc func(err error)            // Called when downloading the GeoIP database succeeds/fails.

	db *geoip2.Reader
}

func (l *geoipLoader) download() error {
	resp, err := http.Get(geoipDownloadURL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	f, err := os.Create(geoipDefaultFilename)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, resp.Body)
	return err
}

func (l *geoipLoader) Load() *geoip2.Reader {
	if l.db == nil {
		if l.Filename == "" {
			// Filename not specified, try default.
			if _, err := os.Stat(geoipDefaultFilename); err == nil {
				// Default already exists, just use it.
				l.Filename = geoipDefaultFilename
			} else if os.IsNotExist(err) {
				// Default doesn't exist, download it.
				l.DownloadFunc(geoipDefaultFilename, geoipDownloadURL)
				err := l.download()
				l.DownloadErrFunc(err)
				if err != nil {
					return nil
				}
				l.Filename = geoipDefaultFilename
			} else {
				// Other error
				return nil
			}
		}
		db, err := geoip2.Open(l.Filename)
		if err != nil {
			return nil
		}
		l.db = db
	}
	return l.db
}