package app

import (
	"agent/client"
	"agent/client/cmds"
	"agent/client/modules"
	"agent/commons/debug"
	"agent/commons/utils"
	"agent/commons/utime"
	"agent/defines/derrs"
	"agent/internal/api"
	"context"
	"sync"
	"time"
)

type AppOpts struct {
	NewStartupInfo     func(ctx context.Context) map[string]any
	Login              func(ctx context.Context, startupInfo map[string]any) (res client.LoginResult, err error)
	DownloadConfig     func(ctx context.Context) (res map[string]any, err error)
	Ping               func(ctx context.Context) (srvTime int64, err error)
	CheckServiceHealth func(ctx context.Context) error
}

func New(config *client.AppConfig, opts ...AppOpts) (context.Context, error) {
	debug.Log("Start App")

	methods := client.ClientMethods{
		NewStartupInfo:     NewStartupInfo,
		Login:              login,
		Ping:               ping,
		DownloadConfig:     downloadConfig,
		CheckServiceHealth: checkServiceHealth,
	}

	if len(opts) > 0 {
		opt := opts[0]
		if opt.NewStartupInfo != nil {
			methods.NewStartupInfo = opt.NewStartupInfo
		}
		if opt.Login != nil {
			methods.Login = opt.Login
		}
		if opt.DownloadConfig != nil {
			methods.DownloadConfig = opt.DownloadConfig
		}
		if opt.Ping != nil {
			methods.Ping = opt.Ping
		}
		if opt.CheckServiceHealth != nil {
			methods.CheckServiceHealth = opt.CheckServiceHealth
		}
	}

	var transport client.ITransport
	for _, newTransport := range client.Transports {
		transport = newTransport(config)
		if transport != nil {
			break
		}
	}

	if transport == nil {
		return nil, derrs.NewUnknownError("No suitable transport found")
	} else {
		debug.Logf(`Using transport: "%s"`, transport.Name())
		debug.Logf(`- Schema: "%s"`, transport.Schema())
		debug.Logf(`- Host: "%s"`, transport.Host())
		debug.Logf(`- Port: "%d"`, transport.Port())
	}

	cl, err := client.NewClient(methods, transport, config)
	if err != nil {
		return nil, err
	}
	commandManager := cmds.NewCommandManager()

	ctx := context.Background()
	ctx = client.SetClient(ctx, cl)
	ctx = cmds.SetCommandManager(ctx, commandManager)
	ctx = utils.CreateAppContext(ctx)

	transport.Init(ctx)

	return ctx, nil
}

func Run(ctx context.Context) {
	var wg sync.WaitGroup
	if err := loadModules(ctx, &wg); err != nil {
		debug.Panic(err)
	}
	wg.Wait()
}

func loadModules(ctx context.Context, wg *sync.WaitGroup) (err error) {
	for _, mod := range modules.List() {
		if err = modules.Load(ctx, mod, wg); err != nil {
			return err
		}
	}
	return nil
}

func login(ctx context.Context, startupInfo map[string]any) (res client.LoginResult, err error) {
	cl := client.ExtractClient(ctx)

	data, err := api.AgentLogin(ctx, cl, startupInfo)
	if err != nil {
		return res, err
	}

	res.Config = data.Config
	res.Token.Value = data.Token.Value
	res.SrvTime = data.SrvTime
	res.MaskKey = data.MaskKey

	return res, nil
}

func ping(ctx context.Context) (srvTime int64, err error) {
	cl := client.ExtractClient(ctx)
	res, err := api.AgentPing(ctx, cl, utime.NanoNow())
	if err != nil {
		return 0, err
	}

	debug.Logf(
		`Ping(SrvTime: "%s", LocalTime: "%s")`,
		time.Unix(0, res.Utime).UTC().Format("2006/01/02 15:04:05"), utime.Now().Format("2006/01/02 15:04:05"),
	)

	return res.Utime, nil
}

func downloadConfig(ctx context.Context) (res map[string]any, err error) {
	cl := client.ExtractClient(ctx)
	return api.AgentConfig(ctx, cl)
}

func checkServiceHealth(ctx context.Context) error {
	cl := client.ExtractClient(ctx)
	return api.ServiceCheckHealth(ctx, cl)
}
