package services

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/google/uuid"
	"github.com/mrhid6/vantage/server/internal/grpc/pb"
)

type commandDispatcher struct {
	mu       sync.RWMutex
	channels map[string]chan *pb.ServerCommand
}

var Dispatcher = &commandDispatcher{
	channels: make(map[string]chan *pb.ServerCommand),
}

func (d *commandDispatcher) Connect(serverID string) chan *pb.ServerCommand {
	ch := make(chan *pb.ServerCommand, 16)
	d.mu.Lock()
	d.channels[serverID] = ch
	d.mu.Unlock()
	return ch
}

func (d *commandDispatcher) Disconnect(serverID string) {
	d.mu.Lock()
	delete(d.channels, serverID)
	d.mu.Unlock()
}

func (d *commandDispatcher) IsConnected(serverID string) bool {
	d.mu.RLock()
	_, ok := d.channels[serverID]
	d.mu.RUnlock()
	return ok
}

func (d *commandDispatcher) dispatch(serverID string, cmd *pb.ServerCommand) error {
	d.mu.RLock()
	ch, ok := d.channels[serverID]
	d.mu.RUnlock()
	if !ok {
		return fmt.Errorf("agent for server %s is not connected", serverID)
	}
	select {
	case ch <- cmd:
		return nil
	default:
		return fmt.Errorf("command queue full for server %s", serverID)
	}
}

func DispatchRunStep(serverID, commandID string, cmd *pb.RunStepCmd) error {
	return Dispatcher.dispatch(serverID, &pb.ServerCommand{CommandId: commandID, RunStep: cmd})
}

func DispatchCleanupWorkspace(serverID, workspaceID string) {
	if !Dispatcher.IsConnected(serverID) {
		return
	}
	_ = Dispatcher.dispatch(serverID, &pb.ServerCommand{
		CommandId:        uuid.New().String(),
		CleanupWorkspace: &pb.CleanupWorkspaceCmd{WorkspaceId: workspaceID},
	})
}

type KeyGenParams struct {
	Label      string
	KeyType    string
	KeySize    int
	Passphrase string
	Comment    string
}

func GetLatestAgentVersion() (string, error) {
	giteaHost := os.Getenv("GITEA_HOST")
	if giteaHost == "" {
		giteaHost = "gitea.example.com"
	}
	url := fmt.Sprintf("https://%s/api/v1/repos/mrhid6/vantage/releases?limit=20", giteaHost)
	resp, err := http.Get(url)
	if err != nil {
		return "", fmt.Errorf("fetch releases: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("Gitea API returned HTTP %d", resp.StatusCode)
	}

	var releases []struct {
		TagName string `json:"tag_name"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
		return "", fmt.Errorf("decode releases: %w", err)
	}

	for _, r := range releases {
		if strings.HasPrefix(r.TagName, "agent/v") {
			return strings.TrimPrefix(r.TagName, "agent/v"), nil
		}
	}
	return "", fmt.Errorf("no agent release found")
}

func DispatchUpdateAgent(serverID string) (string, error) {
	if !Dispatcher.IsConnected(serverID) {
		return "", fmt.Errorf("agent is not connected to the command stream")
	}

	version, err := GetLatestAgentVersion()
	if err != nil {
		return "", fmt.Errorf("get latest version: %w", err)
	}

	giteaHost := os.Getenv("GITEA_HOST")
	if giteaHost == "" {
		giteaHost = "gitea.example.com"
	}

	cmdID := uuid.New().String()
	cmd := &pb.ServerCommand{
		CommandId: cmdID,
		UpdateAgent: &pb.UpdateAgentCmd{
			Version:      version,
			GiteaBaseURL: "https://" + giteaHost,
		},
	}
	if err := Dispatcher.dispatch(serverID, cmd); err != nil {
		return "", err
	}
	return version, nil
}

func DispatchApplyUpdates(serverID string) error {
	if !Dispatcher.IsConnected(serverID) {
		return fmt.Errorf("agent is not connected to the command stream")
	}
	cmd := &pb.ServerCommand{
		CommandId:    uuid.New().String(),
		ApplyUpdates: &pb.ApplyUpdatesCmd{},
	}
	return Dispatcher.dispatch(serverID, cmd)
}

func DispatchDeleteKey(serverID, label string) {
	if !Dispatcher.IsConnected(serverID) {
		return
	}
	cmd := &pb.ServerCommand{
		CommandId: uuid.New().String(),
		DeleteKey: &pb.DeleteKeyCmd{Label: label},
	}
	if err := Dispatcher.dispatch(serverID, cmd); err != nil {

		_ = err
	}
}

func DispatchGenerateKey(serverID string, p KeyGenParams) (string, error) {
	if !Dispatcher.IsConnected(serverID) {
		return "", fmt.Errorf("agent is not connected to the command stream")
	}
	cmdID := uuid.New().String()
	cmd := &pb.ServerCommand{
		CommandId: cmdID,
		GenerateKey: &pb.GenerateKeyCmd{
			Label:      p.Label,
			KeyType:    p.KeyType,
			KeySize:    p.KeySize,
			Passphrase: p.Passphrase,
			Comment:    p.Comment,
		},
	}
	if err := Dispatcher.dispatch(serverID, cmd); err != nil {
		return "", err
	}
	return cmdID, nil
}
