package workspace

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"

	"github.com/samber/mo"
)

type Workspace struct {
	HostPath  string `json:"host_path"`
	Hash      string `json:"hash"`
	MountTag  string `json:"mount_tag"`
	GuestPath string `json:"guest_path"`
}

type Registry struct {
	filePath   string
	workspaces map[string]Workspace
}

// Hash generates an 8-character hash from a path, matching bash behavior:
// echo -n "$path" | sha256sum | cut -c1-8
func Hash(path string) string {
	h := sha256.Sum256([]byte(path))
	return fmt.Sprintf("%x", h)[:8]
}

// NewRegistry creates a new empty registry
func NewRegistry(filePath string) *Registry {
	return &Registry{
		filePath:   filePath,
		workspaces: make(map[string]Workspace),
	}
}

// Load reads the registry from a JSON file
func Load(filePath string) mo.Result[*Registry] {
	registry := NewRegistry(filePath)

	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return mo.Ok(registry)
	}

	data, err := os.ReadFile(filePath)
	if err != nil {
		return mo.Err[*Registry](fmt.Errorf("failed to read workspaces file: %w", err))
	}

	if len(data) == 0 {
		return mo.Ok(registry)
	}

	var workspaceList []Workspace
	if err := json.Unmarshal(data, &workspaceList); err != nil {
		return mo.Err[*Registry](fmt.Errorf("failed to parse workspaces JSON: %w", err))
	}

	for _, ws := range workspaceList {
		registry.workspaces[ws.HostPath] = ws
	}

	return mo.Ok(registry)
}

// Save writes the registry to the JSON file
func (r *Registry) Save() mo.Result[struct{}] {
	dir := filepath.Dir(r.filePath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return mo.Err[struct{}](fmt.Errorf("failed to create directory: %w", err))
	}

	workspaceList := make([]Workspace, 0, len(r.workspaces))
	for _, ws := range r.workspaces {
		workspaceList = append(workspaceList, ws)
	}

	data, err := json.MarshalIndent(workspaceList, "", "  ")
	if err != nil {
		return mo.Err[struct{}](fmt.Errorf("failed to marshal JSON: %w", err))
	}

	if err := os.WriteFile(r.filePath, data, 0644); err != nil {
		return mo.Err[struct{}](fmt.Errorf("failed to write workspaces file: %w", err))
	}

	return mo.Ok(struct{}{})
}

// Register adds a workspace to the registry if it doesn't already exist
func (r *Registry) Register(hostPath string) mo.Result[*Workspace] {
	absPath, err := filepath.Abs(hostPath)
	if err != nil {
		return mo.Err[*Workspace](fmt.Errorf("failed to resolve absolute path: %w", err))
	}

	if existing, exists := r.workspaces[absPath]; exists {
		return mo.Ok(&existing)
	}

	hash := Hash(absPath)
	ws := Workspace{
		HostPath:  absPath,
		Hash:      hash,
		MountTag:  fmt.Sprintf("ws_%s", hash),
		GuestPath: fmt.Sprintf("/workspace/%s", hash),
	}

	r.workspaces[absPath] = ws
	return mo.Ok(&ws)
}

// List returns all registered workspaces
func (r *Registry) List() []Workspace {
	result := make([]Workspace, 0, len(r.workspaces))
	for _, ws := range r.workspaces {
		result = append(result, ws)
	}
	return result
}

// Find looks up a workspace by host path
func (r *Registry) Find(hostPath string) mo.Option[Workspace] {
	absPath, err := filepath.Abs(hostPath)
	if err != nil {
		if ws, exists := r.workspaces[hostPath]; exists {
			return mo.Some(ws)
		}
		return mo.None[Workspace]()
	}

	if ws, exists := r.workspaces[absPath]; exists {
		return mo.Some(ws)
	}
	return mo.None[Workspace]()
}
