package proxy

import (
	"fmt"
	"net/url"
	"time"

	D "github.com/yusing/go-proxy/internal/docker"
	E "github.com/yusing/go-proxy/internal/error"
	"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
	net "github.com/yusing/go-proxy/internal/net/types"
	T "github.com/yusing/go-proxy/internal/proxy/fields"
	"github.com/yusing/go-proxy/internal/types"
	"github.com/yusing/go-proxy/internal/watcher/health"
)

type (
	ReverseProxyEntry struct { // real model after validation
		Alias        T.Alias                  `json:"alias"`
		Scheme       T.Scheme                 `json:"scheme"`
		URL          net.URL                  `json:"url"`
		NoTLSVerify  bool                     `json:"no_tls_verify"`
		PathPatterns T.PathPatterns           `json:"path_patterns"`
		HealthCheck  health.HealthCheckConfig `json:"healthcheck"`
		LoadBalance  loadbalancer.Config      `json:"load_balance"`
		Middlewares  D.NestedLabelMap         `json:"middlewares"`

		/* Docker only */
		IdleTimeout      time.Duration `json:"idle_timeout"`
		WakeTimeout      time.Duration `json:"wake_timeout"`
		StopMethod       T.StopMethod  `json:"stop_method"`
		StopTimeout      int           `json:"stop_timeout"`
		StopSignal       T.Signal      `json:"stop_signal"`
		DockerHost       string        `json:"docker_host"`
		ContainerName    string        `json:"container_name"`
		ContainerID      string        `json:"container_id"`
		ContainerRunning bool          `json:"container_running"`
	}
	StreamEntry struct {
		Alias       T.Alias                  `json:"alias"`
		Scheme      T.StreamScheme           `json:"scheme"`
		Host        T.Host                   `json:"host"`
		Port        T.StreamPort             `json:"port"`
		Healthcheck health.HealthCheckConfig `json:"healthcheck"`
	}
)

func (rp *ReverseProxyEntry) UseIdleWatcher() bool {
	return rp.IdleTimeout > 0 && rp.DockerHost != ""
}

func (rp *ReverseProxyEntry) IsDocker() bool {
	return rp.DockerHost != ""
}

func (rp *ReverseProxyEntry) IsZeroPort() bool {
	return rp.URL.Port() == "0"
}

func ValidateEntry(m *types.RawEntry) (any, E.NestedError) {
	m.FillMissingFields()

	scheme, err := T.NewScheme(m.Scheme)
	if err != nil {
		return nil, err
	}

	var entry any
	e := E.NewBuilder("error validating entry")
	if scheme.IsStream() {
		entry = validateStreamEntry(m, e)
	} else {
		entry = validateRPEntry(m, scheme, e)
	}
	if err := e.Build(); err != nil {
		return nil, err
	}
	return entry, nil
}

func validateRPEntry(m *types.RawEntry, s T.Scheme, b E.Builder) *ReverseProxyEntry {
	var stopTimeOut time.Duration

	host, err := T.ValidateHost(m.Host)
	b.Add(err)

	port, err := T.ValidatePort(m.Port)
	b.Add(err)

	pathPatterns, err := T.ValidatePathPatterns(m.PathPatterns)
	b.Add(err)

	url, err := E.Check(url.Parse(fmt.Sprintf("%s://%s:%d", s, host, port)))
	b.Add(err)

	idleTimeout, err := T.ValidateDurationPostitive(m.IdleTimeout)
	b.Add(err)

	wakeTimeout, err := T.ValidateDurationPostitive(m.WakeTimeout)
	b.Add(err)

	stopMethod, err := T.ValidateStopMethod(m.StopMethod)
	b.Add(err)

	if stopMethod == T.StopMethodStop {
		stopTimeOut, err = T.ValidateDurationPostitive(m.StopTimeout)
		b.Add(err)
	}

	stopSignal, err := T.ValidateSignal(m.StopSignal)
	b.Add(err)

	if err != nil {
		return nil
	}

	return &ReverseProxyEntry{
		Alias:            T.NewAlias(m.Alias),
		Scheme:           s,
		URL:              net.NewURL(url),
		NoTLSVerify:      m.NoTLSVerify,
		PathPatterns:     pathPatterns,
		HealthCheck:      m.HealthCheck,
		LoadBalance:      m.LoadBalance,
		Middlewares:      m.Middlewares,
		IdleTimeout:      idleTimeout,
		WakeTimeout:      wakeTimeout,
		StopMethod:       stopMethod,
		StopTimeout:      int(stopTimeOut.Seconds()), // docker api takes integer seconds for timeout argument
		StopSignal:       stopSignal,
		DockerHost:       m.DockerHost,
		ContainerName:    m.ContainerName,
		ContainerID:      m.ContainerID,
		ContainerRunning: m.Running,
	}
}

func validateStreamEntry(m *types.RawEntry, b E.Builder) *StreamEntry {
	host, err := T.ValidateHost(m.Host)
	b.Add(err)

	port, err := T.ValidateStreamPort(m.Port)
	b.Add(err)

	scheme, err := T.ValidateStreamScheme(m.Scheme)
	b.Add(err)

	if b.HasError() {
		return nil
	}

	return &StreamEntry{
		Alias:       T.NewAlias(m.Alias),
		Scheme:      *scheme,
		Host:        host,
		Port:        port,
		Healthcheck: m.HealthCheck,
	}
}
