package route

import (
	"strings"

	"github.com/yusing/go-proxy/internal/docker"
	E "github.com/yusing/go-proxy/internal/error"
	url "github.com/yusing/go-proxy/internal/net/types"
	"github.com/yusing/go-proxy/internal/route/entry"
	"github.com/yusing/go-proxy/internal/route/types"
	"github.com/yusing/go-proxy/internal/task"
	U "github.com/yusing/go-proxy/internal/utils"
	F "github.com/yusing/go-proxy/internal/utils/functional"
)

type (
	Route struct {
		_ U.NoCopy
		impl
		Type  types.RouteType
		Entry *RawEntry
	}
	Routes = F.Map[string, *Route]

	impl interface {
		types.Route
		task.TaskStarter
		task.TaskFinisher
		String() string
		TargetURL() url.URL
	}
	RawEntry   = types.RawEntry
	RawEntries = types.RawEntries
)

// function alias.
var (
	NewRoutes       = F.NewMap[Routes]
	NewProxyEntries = types.NewProxyEntries
)

func (rt *Route) Container() *docker.Container {
	if rt.Entry.Container == nil {
		return docker.DummyContainer
	}
	return rt.Entry.Container
}

func NewRoute(raw *RawEntry) (*Route, E.Error) {
	raw.Finalize()
	en, err := entry.ValidateEntry(raw)
	if err != nil {
		return nil, err
	}

	var t types.RouteType
	var rt impl

	switch e := en.(type) {
	case *entry.StreamEntry:
		t = types.RouteTypeStream
		rt, err = NewStreamRoute(e)
	case *entry.ReverseProxyEntry:
		t = types.RouteTypeReverseProxy
		rt, err = NewHTTPRoute(e)
	default:
		panic("bug: should not reach here")
	}
	if err != nil {
		return nil, err
	}
	return &Route{
		impl:  rt,
		Type:  t,
		Entry: raw,
	}, nil
}

func FromEntries(provider string, entries RawEntries) (Routes, E.Error) {
	b := E.NewBuilder("errors in routes")

	routes := NewRoutes()
	entries.RangeAllParallel(func(alias string, en *RawEntry) {
		if en == nil {
			en = new(RawEntry)
		}
		en.Alias = alias
		en.Provider = provider
		if strings.HasPrefix(alias, "x-") { // x properties
			return
		}
		r, err := NewRoute(en)
		switch {
		case err != nil:
			b.Add(err.Subject(alias))
		case entry.ShouldNotServe(r):
			return
		default:
			routes.Store(alias, r)
		}
	})

	return routes, b.Error()
}