package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/prism/proxy/internal/country"
	"github.com/prism/proxy/internal/mihomoapi"
	"github.com/prism/proxy/internal/store"
)

type OutboundMember struct {
	RuntimeName string `json:"runtime_name"`
	Name        string `json:"name"`
}

type OutboundGroup struct {
	RuntimeName string           `json:"runtime_name"`
	Name        string           `json:"name"`
	Type        string           `json:"type"`
	Source      string           `json:"source"`
	Now         string           `json:"now"`
	NowName     string           `json:"now_name"`
	Selectable  bool             `json:"selectable"`
	IsCountry   bool             `json:"is_country"`
	Members     []OutboundMember `json:"members"`
}

type OutboundOverview struct {
	DefaultPolicy string          `json:"default_policy"`
	Groups        []OutboundGroup `json:"groups"`
}

func (a *App) OutboundOverview(ctx context.Context) (OutboundOverview, error) {
	settings, err := a.Store.GetSettings(ctx)
	if err != nil {
		return OutboundOverview{}, err
	}
	defaultPolicy := settings["default_policy"]
	if defaultPolicy == "" {
		defaultPolicy = "DIRECT"
	}

	base, secret, err := a.MihomoAPIBase(ctx)
	if err != nil {
		return OutboundOverview{}, err
	}
	live, err := mihomoapi.New(base, secret).ListProxiesCached(8 * time.Second)
	if err != nil {
		return OutboundOverview{}, fmt.Errorf("mihomo proxies: %w", err)
	}

	nodes, err := a.Store.ListProxyNodes(ctx, "", nil)
	if err != nil {
		return OutboundOverview{}, err
	}
	groups, err := a.Store.ListProxyGroups(ctx)
	if err != nil {
		return OutboundOverview{}, err
	}
	display := buildDisplayMap(nodes, groups)

	var outGroups []OutboundGroup
	seen := map[string]bool{}
	for _, g := range groups {
		if !g.Enabled {
			continue
		}
		state, ok := live[g.RuntimeName]
		if !ok || !mihomoapi.IsPolicyGroupType(state.Type) {
			continue
		}
		seen[g.RuntimeName] = true
		outGroups = append(outGroups, buildOutboundGroup(g.RuntimeName, g.Name, g.Source, state, display))
	}
	for name, state := range live {
		if seen[name] || !mihomoapi.IsPolicyGroupType(state.Type) {
			continue
		}
		outGroups = append(outGroups, buildOutboundGroup(name, display[name], "runtime", state, display))
	}

	return OutboundOverview{
		DefaultPolicy: defaultPolicy,
		Groups:        outGroups,
	}, nil
}

func (a *App) SelectOutboundGroup(ctx context.Context, groupRuntime, proxyRuntime string) error {
	base, secret, err := a.MihomoAPIBase(ctx)
	if err != nil {
		return err
	}
	if err := mihomoapi.New(base, secret).SelectProxy(groupRuntime, proxyRuntime); err != nil {
		return err
	}
	_ = a.Store.AddAuditLog(ctx, "select", "outbound", groupRuntime+" -> "+proxyRuntime)
	return nil
}

func (a *App) SetDefaultPolicy(ctx context.Context, policy string) error {
	if err := a.Store.SetSettings(ctx, map[string]string{"default_policy": policy}); err != nil {
		return err
	}
	return a.ReloadEngine(ctx)
}

func buildOutboundGroup(runtimeName, name, source string, state mihomoapi.ProxyState, display map[string]string) OutboundGroup {
	if name == "" {
		name = runtimeName
	}
	autoOnly := state.Type == "URLTest" || state.Type == "Fallback"
	members := make([]OutboundMember, 0, len(state.All))
	for _, m := range state.All {
		if autoOnly && (m == "DIRECT" || m == "REJECT" || m == "REJECT-DROP") {
			continue
		}
		if m == "DIRECT" || m == "REJECT" || m == "REJECT-DROP" {
			members = append(members, OutboundMember{RuntimeName: m, Name: m})
			continue
		}
		members = append(members, OutboundMember{
			RuntimeName: m,
			Name:        displayName(display, m),
		})
	}
	return OutboundGroup{
		RuntimeName: runtimeName,
		Name:        name,
		Type:        state.Type,
		Source:      source,
		Now:         state.Now,
		NowName:     displayName(display, state.Now),
		Selectable:  state.Type == "Selector",
		IsCountry:   strings.HasPrefix(runtimeName, "country_"),
		Members:     members,
	}
}

func buildDisplayMap(nodes []store.ProxyNode, groups []store.ProxyGroup) map[string]string {
	m := map[string]string{
		"DIRECT": "DIRECT",
		"REJECT": "REJECT",
	}
	seen := map[string]bool{}
	for _, n := range nodes {
		m[n.RuntimeName] = n.Name
		if n.Country != "" && !seen[n.Country] {
			seen[n.Country] = true
			m[country.GroupRuntime(n.Country)] = country.Name(n.Country) + "（国家池）"
		}
	}
	for _, g := range groups {
		m[g.RuntimeName] = g.Name
	}
	return m
}

func displayName(display map[string]string, runtime string) string {
	if name, ok := display[runtime]; ok && name != "" {
		return name
	}
	return runtime
}
