package workspace

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/yaoapp/yao/tai"
	"github.com/yaoapp/yao/tai/registry"
	taitypes "github.com/yaoapp/yao/tai/types"
	"github.com/yaoapp/yao/tai/volume"
	taiworkspace "github.com/yaoapp/yao/tai/workspace"
)

var mgr = NewManager()

// M returns the global Manager.
func M() *Manager {
	return mgr
}

// Manager owns workspace CRUD, file I/O, and node management.
type Manager struct{}

// NewManager creates a workspace manager.
func NewManager() *Manager {
	return &Manager{}
}

// Create allocates storage on the target node and persists metadata.
func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Workspace, error) {
	if opts.Node == "" {
		return nil, ErrNodeMissing
	}

	res, ok := tai.GetResources(opts.Node)
	if !ok {
		return nil, ErrNodeOffline
	}

	id := opts.ID
	if id == "" {
		id = generateID()
	}

	now := time.Now().UTC()
	ws := &Workspace{
		ID:        id,
		Name:      opts.Name,
		Owner:     opts.Owner,
		Node:      opts.Node,
		Labels:    opts.Labels,
		CreatedAt: now,
		UpdatedAt: now,
	}

	vol := res.Volume
	if err := vol.MkdirAll(ctx, id, "."); err != nil {
		return nil, fmt.Errorf("workspace: create directory: %w", err)
	}

	data, err := marshalMeta(ws)
	if err != nil {
		return nil, err
	}
	if err := vol.WriteFile(ctx, id, metadataFile, data, 0644); err != nil {
		return nil, fmt.Errorf("workspace: write metadata: %w", err)
	}

	return ws, nil
}

// Get returns a workspace by ID.
func (m *Manager) Get(ctx context.Context, id string) (*Workspace, error) {
	for _, snap := range listNodes() {
		res, ok := tai.GetResources(snap.TaiID)
		if !ok {
			continue
		}
		ws, err := readMeta(ctx, res.Volume, id)
		if err != nil {
			continue
		}
		if ws.Node == "" {
			ws.Node = snap.TaiID
		}
		return ws, nil
	}
	return nil, ErrNotFound
}

// List returns workspaces, optionally filtered by owner and/or node.
func (m *Manager) List(ctx context.Context, opts ListOptions) ([]*Workspace, error) {
	var result []*Workspace
	for _, snap := range listNodes() {
		if opts.Node != "" && snap.TaiID != opts.Node {
			continue
		}
		res, ok := tai.GetResources(snap.TaiID)
		if !ok {
			continue
		}
		entries, err := res.Volume.ListDir(ctx, "", ".")
		if err != nil {
			continue
		}
		for _, e := range entries {
			if !e.IsDir {
				continue
			}
			ws, err := readMeta(ctx, res.Volume, e.Path)
			if err != nil {
				continue
			}
			if ws.Node == "" {
				ws.Node = snap.TaiID
			}
			if opts.Owner != "" && ws.Owner != opts.Owner {
				continue
			}
			result = append(result, ws)
		}
	}
	return result, nil
}

// Update modifies workspace metadata (Name, Labels).
func (m *Manager) Update(ctx context.Context, id string, opts UpdateOptions) (*Workspace, error) {
	ws, vol, err := m.resolve(ctx, id)
	if err != nil {
		return nil, err
	}

	if opts.Name != nil {
		ws.Name = *opts.Name
	}
	if opts.Labels != nil {
		ws.Labels = opts.Labels
	}
	ws.UpdatedAt = time.Now().UTC()

	data, err := marshalMeta(ws)
	if err != nil {
		return nil, err
	}
	if err := vol.WriteFile(ctx, id, metadataFile, data, 0644); err != nil {
		return nil, fmt.Errorf("workspace: write metadata: %w", err)
	}
	return ws, nil
}

// Delete removes workspace storage from the node.
func (m *Manager) Delete(ctx context.Context, id string, force bool) error {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return err
	}
	if err := vol.Remove(ctx, id, ".", true); err != nil {
		return fmt.Errorf("workspace: remove: %w", err)
	}
	return nil
}

// Nodes returns all registered Tai nodes with their online status.
func (m *Manager) Nodes() []NodeInfo {
	nodes := listNodes()
	result := make([]NodeInfo, 0, len(nodes))
	for _, snap := range nodes {
		result = append(result, NodeInfo{
			Name:   snap.TaiID,
			Online: snap.Status == "online" || snap.Status == "",
		})
	}
	return result
}

// FS returns an fs.FS-compatible filesystem for the given workspace.
func (m *Manager) FS(ctx context.Context, id string) (taiworkspace.FS, error) {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return nil, err
	}
	return taiworkspace.New(vol, id), nil
}

// ReadFile reads a file from the workspace.
func (m *Manager) ReadFile(ctx context.Context, id string, path string) ([]byte, error) {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return nil, err
	}
	data, _, err := vol.ReadFile(ctx, id, path)
	return data, err
}

// WriteFile writes a file to the workspace.
func (m *Manager) WriteFile(ctx context.Context, id string, path string, data []byte, perm os.FileMode) error {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return err
	}
	return vol.WriteFile(ctx, id, path, data, perm)
}

// ListDir lists entries in a workspace directory.
func (m *Manager) ListDir(ctx context.Context, id string, path string) ([]DirEntry, error) {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return nil, err
	}
	entries, err := vol.ListDir(ctx, id, path)
	if err != nil {
		return nil, err
	}
	result := make([]DirEntry, len(entries))
	for i, e := range entries {
		result[i] = DirEntry{
			Name:    e.Path,
			IsDir:   e.IsDir,
			Size:    e.Size,
			ModTime: e.Mtime,
		}
	}
	return result, nil
}

// Remove deletes a file or directory from the workspace.
func (m *Manager) Remove(ctx context.Context, id string, path string) error {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return err
	}
	return vol.Remove(ctx, id, path, true)
}

// Rename renames a file or directory within the workspace.
func (m *Manager) Rename(ctx context.Context, id string, oldPath, newPath string) error {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return err
	}
	return vol.Rename(ctx, id, oldPath, newPath)
}

// MkdirAll creates a directory (and parents) in the workspace.
func (m *Manager) MkdirAll(ctx context.Context, id string, path string) error {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return err
	}
	return vol.MkdirAll(ctx, id, path)
}

// Volume returns the Volume interface for the node hosting the given workspace.
func (m *Manager) Volume(ctx context.Context, id string) (volume.Volume, string, error) {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return nil, "", err
	}
	return vol, id, nil
}

// NodeForWorkspace returns the node name for a given workspace ID.
func (m *Manager) NodeForWorkspace(ctx context.Context, id string) (string, error) {
	ws, _, err := m.resolve(ctx, id)
	if err != nil {
		return "", err
	}
	return ws.Node, nil
}

// MountPath returns the host-side directory path for a workspace.
func (m *Manager) MountPath(ctx context.Context, id string) (string, error) {
	_, vol, err := m.resolve(ctx, id)
	if err != nil {
		return "", err
	}
	return vol.Abs(ctx, id, ".")
}

// --- internal ---

// resolve finds the workspace and its Volume by scanning all registered nodes.
func (m *Manager) resolve(ctx context.Context, id string) (*Workspace, volume.Volume, error) {
	for _, snap := range listNodes() {
		res, ok := tai.GetResources(snap.TaiID)
		if !ok {
			continue
		}
		ws, err := readMeta(ctx, res.Volume, id)
		if err != nil {
			continue
		}
		return ws, res.Volume, nil
	}
	return nil, nil, ErrNotFound
}

func readMeta(ctx context.Context, vol volume.Volume, id string) (*Workspace, error) {
	data, _, err := vol.ReadFile(ctx, id, metadataFile)
	if err != nil {
		return nil, err
	}
	return unmarshalMeta(data)
}

func listNodes() []taitypes.NodeMeta {
	reg := registry.Global()
	if reg == nil {
		return nil
	}
	return reg.List()
}

// DirEntry represents a file or directory entry in a workspace listing.
type DirEntry struct {
	Name    string    `json:"name"`
	IsDir   bool      `json:"is_dir"`
	Size    int64     `json:"size"`
	ModTime time.Time `json:"mod_time,omitempty"`
}
