package tunnel

import (
	"errors"
	"sync"
	"time"

	"github.com/go-gost/core/logger"
	"github.com/go-gost/gost.plus/config"
	xconfig "github.com/go-gost/x/config"
	_ "github.com/go-gost/x/connector/tunnel"
	_ "github.com/go-gost/x/dialer/ws"
	xservice "github.com/go-gost/x/service"
)

const (
	EndpointAddr = "gost.plus"
	ServerName   = "tunnel.gost.plus"
	ServerAddr   = ServerName + ":443"
)

const (
	FileTunnel = "file"
	HTTPTunnel = "http"
	TCPTunnel  = "tcp"
	UDPTunnel  = "udp"
)

var (
	ErrTunnelClosed = errors.New("tunnel closed")
)

type Options struct {
	ID        string
	Name      string
	Endpoint  string
	Hostname  string
	Username  string
	Password  string
	EnableTLS bool
	Keepalive bool
	TTL       int
	CreatedAt time.Time
	Stats     config.ServiceStats
}

type Option func(opts *Options)

func IDOption(id string) Option {
	return func(opts *Options) {
		opts.ID = id
	}
}

func NameOption(name string) Option {
	return func(opts *Options) {
		opts.Name = name
	}
}

func EndpointOption(endpoint string) Option {
	return func(opts *Options) {
		opts.Endpoint = endpoint
	}
}

func HostnameOption(hostname string) Option {
	return func(opts *Options) {
		opts.Hostname = hostname
	}
}

func UsernameOption(username string) Option {
	return func(opts *Options) {
		opts.Username = username
	}
}

func PasswordOption(password string) Option {
	return func(opts *Options) {
		opts.Password = password
	}
}

func EnableTLSOption(b bool) Option {
	return func(opts *Options) {
		opts.EnableTLS = b
	}
}

func KeepaliveOption(b bool) Option {
	return func(opts *Options) {
		opts.Keepalive = b
	}
}

func TTLOption(ttl int) Option {
	return func(opts *Options) {
		opts.TTL = ttl
	}
}

func CreatedAtOption(createdAt time.Time) Option {
	return func(opts *Options) {
		opts.CreatedAt = createdAt
	}
}

type ServiceStatus interface {
	Status() *xservice.Status
}

type Tunnel interface {
	ID() string
	Type() string
	Name() string
	Endpoint() string
	Entrypoint() string
	Options() Options
	Run() error
	Status() *xservice.Status
	Stats() config.ServiceStats
	SetStats(stats config.ServiceStats)
	Favorite(b bool)
	IsFavorite() bool
	Close() error
	IsClosed() bool
	Err() error
}

type tunnelList struct {
	list []Tunnel
	mux  sync.RWMutex
}

var (
	tunnels tunnelList
)

func Count() int {
	tunnels.mux.RLock()
	defer tunnels.mux.RUnlock()
	return len(tunnels.list)
}

func Add(s Tunnel) {
	tunnels.mux.Lock()
	defer tunnels.mux.Unlock()
	tunnels.list = append(tunnels.list, s)
}

func Set(s Tunnel) {
	if s == nil {
		return
	}
	t := Get(s.ID())
	if t == nil {
		return
	}
	s.Favorite(t.IsFavorite())

	tunnels.mux.Lock()
	defer tunnels.mux.Unlock()

	for i, sv := range tunnels.list {
		if sv != nil && sv.ID() == s.ID() {
			tunnels.list[i] = s
		}
	}
}

func GetIndex(index int) Tunnel {
	tunnels.mux.RLock()
	defer tunnels.mux.RUnlock()
	if index < 0 || index >= len(tunnels.list) {
		return nil
	}
	return tunnels.list[index]
}

func Get(id string) Tunnel {
	tunnels.mux.RLock()
	defer tunnels.mux.RUnlock()

	for _, s := range tunnels.list {
		if s != nil && s.ID() == id {
			return s
		}
	}
	return nil
}

func Delete(id string) {
	tunnels.mux.Lock()
	defer tunnels.mux.Unlock()

	for i, s := range tunnels.list {
		if s != nil && s.ID() == id {
			s.Close()
			tunnels.list[i] = nil
			return
		}
	}
}

func ChainConfig(id string, name string) *xconfig.ChainConfig {
	return &xconfig.ChainConfig{
		Name: name,
		Hops: []*xconfig.HopConfig{
			{
				Name: name,
				Nodes: []*xconfig.NodeConfig{
					{
						Name: name,
						Addr: ServerAddr,
						Connector: &xconfig.ConnectorConfig{
							Type:     "tunnel",
							Metadata: map[string]any{"tunnel.id": id},
						},
						Dialer: &xconfig.DialerConfig{
							Type: "wss",
							TLS: &xconfig.TLSConfig{
								Secure:     true,
								ServerName: ServerName,
							},
						},
					},
				},
			},
		},
	}
}

func LoadConfig() {
	for _, cfg := range config.Get().Tunnels {
		if cfg == nil {
			continue
		}

		tun := createTunnel(cfg.Type, Options{
			ID:        cfg.ID,
			Name:      cfg.Name,
			Endpoint:  cfg.Endpoint,
			Hostname:  cfg.Hostname,
			Username:  cfg.Username,
			Password:  cfg.Password,
			EnableTLS: cfg.EnableTLS,
			CreatedAt: cfg.CreatedAt,
			Stats:     cfg.Stats,
		})
		if tun == nil {
			continue
		}

		if cfg.Closed {
			tun.Close()
		} else {
			tun.Run()
		}

		tun.Favorite(cfg.Favorite)
		Add(tun)
	}
}

func SaveConfig() error {
	cfg := config.Get()
	cfg.Tunnels = nil

	for i := 0; i < Count(); i++ {
		tun := GetIndex(i)
		if tun == nil {
			continue
		}

		opts := tun.Options()

		cfg.Tunnels = append(cfg.Tunnels, &config.Tunnel{
			ID:        tun.ID(),
			Name:      tun.Name(),
			Type:      tun.Type(),
			Endpoint:  tun.Endpoint(),
			Hostname:  opts.Hostname,
			Username:  opts.Username,
			Password:  opts.Password,
			EnableTLS: opts.EnableTLS,
			Favorite:  tun.IsFavorite(),
			Closed:    tun.IsClosed(),
			CreatedAt: opts.CreatedAt,
			Stats:     tun.Stats(),
		})
	}

	config.Set(cfg)

	if err := cfg.Write(); err != nil {
		logger.Default().Error(err)
		return err
	}
	return nil
}

func createTunnel(st string, opts Options) (t Tunnel) {
	options := []Option{
		IDOption(opts.ID),
		NameOption(opts.Name),
		EndpointOption(opts.Endpoint),
		HostnameOption(opts.Hostname),
		UsernameOption(opts.Username),
		PasswordOption(opts.Password),
		EnableTLSOption(opts.EnableTLS),
		CreatedAtOption(opts.CreatedAt),
	}

	switch st {
	case FileTunnel:
		t = NewFileTunnel(options...)
	case HTTPTunnel:
		t = NewHTTPTunnel(options...)
	case TCPTunnel:
		t = NewTCPTunnel(options...)
	case UDPTunnel:
		t = NewUDPTunnel(options...)
	default:
		return nil
	}

	t.SetStats(opts.Stats)
	return
}
