package service

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/prism/proxy/internal/cache"
	"github.com/prism/proxy/internal/configbuilder"
	"github.com/prism/proxy/internal/core"
	"github.com/prism/proxy/internal/geodata"
	applog "github.com/prism/proxy/internal/log"
	"github.com/prism/proxy/internal/mihomoapi"
	"github.com/prism/proxy/internal/store"
	"github.com/prism/proxy/internal/subscription"
)

type App struct {
	Store           *store.Store
	Cache           *cache.Cache
	Engine          *core.Engine
	Builder         *configbuilder.Builder
	Subscription    *subscription.Service
	Logs            *applog.Collector
	DataDir         string
	LastReloadError string

	engineMu sync.RWMutex
	engineOK bool
	engineAt time.Time
}

func (a *App) ReloadEngine(ctx context.Context) error {
	geodata.InitHomeDir(a.DataDir)
	settings, err := a.Store.GetSettings(ctx)
	if err != nil {
		return err
	}
	geodata.ApplyMihomoURLs(geodata.ConfigFromSettings(settings))

	yaml, err := a.Builder.Build(ctx)
	if err != nil {
		return err
	}
	apiPort := settings["mihomo_api_port"]
	if apiPort == "" {
		apiPort = "9090"
	}
	secret := settings["api_secret"]
	a.Engine.Configure("127.0.0.1:"+apiPort, secret)

	if err := a.applyConfig(ctx, yaml); err != nil {
		a.Logs.Add("warn", "prism", "full config failed, trying minimal: "+err.Error())
		minimal, mErr := a.Builder.BuildMinimal(ctx)
		if mErr != nil {
			return err
		}
		if mErr := a.applyConfig(ctx, minimal); mErr != nil {
			a.LastReloadError = err.Error()
			if last := a.Cache.GetLastYAML(); last != nil {
				if fallbackErr := a.Engine.Reload(last); fallbackErr == nil {
					a.Logs.Add("warn", "prism", "kept previous engine config after reload failure")
				}
			}
			return err
		}
		a.LastReloadError = "rules simplified: " + err.Error()
		a.Logs.Add("warn", "prism", a.LastReloadError)
		return nil
	}
	return nil
}

func (a *App) applyConfig(ctx context.Context, yaml []byte) error {
	if err := a.Engine.Reload(yaml); err != nil {
		return err
	}
	a.LastReloadError = ""
	a.Cache.SetLastYAML(yaml)
	a.Logs.Add("info", "prism", "engine reloaded successfully")
	a.engineMu.Lock()
	a.engineOK = true
	a.engineAt = time.Now()
	a.engineMu.Unlock()
	_ = a.Store.AddAuditLog(ctx, "reload", "engine", "config applied")
	return nil
}

func (a *App) MihomoAPIBase(ctx context.Context) (string, string, error) {
	settings, err := a.Store.GetSettings(ctx)
	if err != nil {
		return "", "", err
	}
	port := settings["mihomo_api_port"]
	if port == "" {
		port = "9090"
	}
	return fmt.Sprintf("http://127.0.0.1:%s", port), settings["api_secret"], nil
}

func (a *App) EngineReachable(ctx context.Context) bool {
	if a.Engine.Running() {
		return true
	}
	a.engineMu.RLock()
	if time.Since(a.engineAt) < 15*time.Second {
		ok := a.engineOK
		a.engineMu.RUnlock()
		return ok
	}
	a.engineMu.RUnlock()

	ok := a.probeEngine(ctx)
	a.engineMu.Lock()
	a.engineOK = ok
	a.engineAt = time.Now()
	a.engineMu.Unlock()
	return ok
}

// EngineStatusCached returns last-known engine reachability without blocking on Mihomo.
func (a *App) EngineStatusCached() bool {
	if a.Engine.Running() {
		return true
	}
	a.engineMu.RLock()
	defer a.engineMu.RUnlock()
	return a.engineOK
}

func (a *App) RefreshEngineStatusAsync() {
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		ok := a.probeEngine(ctx)
		a.engineMu.Lock()
		a.engineOK = ok
		a.engineAt = time.Now()
		a.engineMu.Unlock()
	}()
}

func (a *App) probeEngine(ctx context.Context) bool {
	base, secret, err := a.MihomoAPIBase(ctx)
	if err != nil {
		return false
	}
	return mihomoapi.New(base, secret).Reachable()
}
