package connection

import (
	"encoding/json"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"sync"
)

// Machine represents a managed machine in the federation.
type Machine struct {
	Name     string `json:"name"`
	Type     string `json:"type"`      // "local", "ssh"
	Host     string `json:"host"`      // for ssh: user@host
	KeyPath  string `json:"key_path"`  // SSH private key path
	TownPath string `json:"town_path"` // Path to town root on remote
}

// registryData is the JSON file structure.
type registryData struct {
	Version  int                 `json:"version"`
	Machines map[string]*Machine `json:"machines"`
}

// MachineRegistry manages machine configurations and provides Connection instances.
type MachineRegistry struct {
	path     string
	machines map[string]*Machine
	mu       sync.RWMutex
}

// NewMachineRegistry creates a registry from the given config file path.
// If the file doesn't exist, an empty registry is created.
func NewMachineRegistry(configPath string) (*MachineRegistry, error) {
	r := &MachineRegistry{
		path:     configPath,
		machines: make(map[string]*Machine),
	}

	// Load existing config if present
	if err := r.load(); err != nil && !os.IsNotExist(err) {
		return nil, fmt.Errorf("loading registry: %w", err)
	}

	// Ensure "local" machine always exists
	if _, ok := r.machines["local"]; !ok {
		r.machines["local"] = &Machine{
			Name: "local",
			Type: "local",
		}
	}

	return r, nil
}

// load reads the registry from disk.
func (r *MachineRegistry) load() error {
	data, err := os.ReadFile(r.path)
	if err != nil {
		return err
	}

	var rd registryData
	if err := json.Unmarshal(data, &rd); err != nil {
		return fmt.Errorf("parsing registry: %w", err)
	}

	r.machines = rd.Machines
	if r.machines == nil {
		r.machines = make(map[string]*Machine)
	}

	// Populate machine names from keys
	for name, m := range r.machines {
		m.Name = name
	}

	return nil
}

// save writes the registry to disk.
func (r *MachineRegistry) save() error {
	rd := registryData{
		Version:  1,
		Machines: r.machines,
	}

	data, err := json.MarshalIndent(rd, "", "  ")
	if err != nil {
		return fmt.Errorf("marshaling registry: %w", err)
	}

	// Ensure parent directory exists
	dir := filepath.Dir(r.path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("creating config directory: %w", err)
	}

	if err := os.WriteFile(r.path, data, fs.FileMode(0644)); err != nil {
		return fmt.Errorf("writing registry: %w", err)
	}

	return nil
}

// Get returns a machine by name.
func (r *MachineRegistry) Get(name string) (*Machine, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	m, ok := r.machines[name]
	if !ok {
		return nil, fmt.Errorf("machine not found: %s", name)
	}
	return m, nil
}

// Add adds or updates a machine in the registry.
func (r *MachineRegistry) Add(m *Machine) error {
	if m.Name == "" {
		return fmt.Errorf("machine name is required")
	}
	if m.Type == "" {
		return fmt.Errorf("machine type is required")
	}
	if m.Type == "ssh" && m.Host == "" {
		return fmt.Errorf("ssh machine requires host")
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	r.machines[m.Name] = m
	return r.save()
}

// Remove removes a machine from the registry.
func (r *MachineRegistry) Remove(name string) error {
	if name == "local" {
		return fmt.Errorf("cannot remove local machine")
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	if _, ok := r.machines[name]; !ok {
		return fmt.Errorf("machine not found: %s", name)
	}

	delete(r.machines, name)
	return r.save()
}

// List returns all machines in the registry.
func (r *MachineRegistry) List() []*Machine {
	r.mu.RLock()
	defer r.mu.RUnlock()

	result := make([]*Machine, 0, len(r.machines))
	for _, m := range r.machines {
		result = append(result, m)
	}
	return result
}

// Connection returns a Connection for the named machine.
func (r *MachineRegistry) Connection(name string) (Connection, error) {
	m, err := r.Get(name)
	if err != nil {
		return nil, err
	}

	switch m.Type {
	case "local":
		return NewLocalConnection(), nil
	case "ssh":
		// SSH connection not yet implemented
		return nil, fmt.Errorf("ssh connections not yet implemented")
	default:
		return nil, fmt.Errorf("unknown machine type: %s", m.Type)
	}
}

// LocalConnection returns the local connection.
// This is a convenience method for the common case.
func (r *MachineRegistry) LocalConnection() *LocalConnection {
	return NewLocalConnection()
}
