package zfs

import (
	"context"
	"fmt"
	"strings"

	"github.com/example/storage-appliance/internal/domain"
	"github.com/example/storage-appliance/internal/infra/osexec"
)

type Adapter struct {
	Runner osexec.Runner
}

func NewAdapter(runner osexec.Runner) *Adapter { return &Adapter{Runner: runner} }

func (a *Adapter) ListPools(ctx context.Context) ([]domain.Pool, error) {
	out, errOut, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "list", "-H", "-o", "name,health,size")
	_ = errOut
	if err != nil {
		return nil, err
	}
	if code != 0 {
		return nil, fmt.Errorf("zpool list returned %d: %s", code, out)
	}
	var pools []domain.Pool
	lines := strings.Split(strings.TrimSpace(out), "\n")
	for _, l := range lines {
		parts := strings.Split(l, "\t")
		if len(parts) >= 3 {
			pools = append(pools, domain.Pool{Name: parts[0], Health: parts[1], Capacity: parts[2]})
		}
	}
	return pools, nil
}

func (a *Adapter) GetPoolStatus(ctx context.Context, pool string) (domain.PoolHealth, error) {
	out, _, _, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "status", pool)
	if err != nil {
		return domain.PoolHealth{}, err
	}
	// heuristic: find HEALTH: lines or scan lines
	status := "UNKNOWN"
	detail := ""
	for _, line := range strings.Split(out, "\n") {
		if strings.Contains(line, "state:") || strings.Contains(line, "health:") {
			detail = detail + line + "\n"
			if strings.Contains(line, "ONLINE") || strings.Contains(line, "ONLINE") {
				status = "ONLINE"
			}
			if strings.Contains(line, "DEGRADED") {
				status = "DEGRADED"
			}
		}
	}
	return domain.PoolHealth{Pool: pool, Status: status, Detail: detail}, nil
}

func (a *Adapter) CreatePoolSync(ctx context.Context, name string, vdevs []string) error {
	args := append([]string{"create", name}, vdevs...)
	_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", args...)
	if err != nil {
		return err
	}
	if code != 0 {
		return fmt.Errorf("zpool create failed: %s", stderr)
	}
	return nil
}

func (a *Adapter) ListDatasets(ctx context.Context, pool string) ([]domain.Dataset, error) {
	out, _, _, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", "list", "-H", "-o", "name,type", "-r", pool)
	if err != nil {
		return nil, err
	}
	var res []domain.Dataset
	for _, l := range strings.Split(strings.TrimSpace(out), "\n") {
		parts := strings.Split(l, "\t")
		if len(parts) >= 2 {
			res = append(res, domain.Dataset{Name: parts[0], Pool: pool, Type: parts[1]})
		}
	}
	return res, nil
}

func (a *Adapter) CreateDataset(ctx context.Context, name string, props map[string]string) error {
	args := []string{"create", name}
	for k, v := range props {
		args = append(args, "-o", fmt.Sprintf("%s=%s", k, v))
	}
	_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", args...)
	if err != nil {
		return err
	}
	if code != 0 {
		return fmt.Errorf("zfs create failed: %s", stderr)
	}
	return nil
}

// CreateZVol creates a block device zvol with the given size and optional props
func (a *Adapter) CreateZVol(ctx context.Context, name, size string, props map[string]string) error {
	args := []string{"create", "-V", size, name}
	for k, v := range props {
		args = append([]string{"create", "-o", fmt.Sprintf("%s=%s", k, v)}, args...)
	}
	// Note: above building may produce repeated 'create' parts - keep simple: build args now
	// We'll just construct a direct zfs create -V size -o prop=val name
	args = []string{"create", "-V", size}
	for k, v := range props {
		args = append(args, "-o", fmt.Sprintf("%s=%s", k, v))
	}
	args = append(args, name)
	out, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", args...)
	_ = out
	if err != nil {
		return err
	}
	if code != 0 {
		return fmt.Errorf("zfs create vol failed: %s", stderr)
	}
	return nil
}

func (a *Adapter) Snapshot(ctx context.Context, dataset, snapName string) error {
	name := fmt.Sprintf("%s@%s", dataset, snapName)
	_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", "snapshot", name)
	if err != nil {
		return err
	}
	if code != 0 {
		return fmt.Errorf("zfs snapshot failed: %s", stderr)
	}
	return nil
}

func (a *Adapter) ScrubStart(ctx context.Context, pool string) error {
	_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "scrub", pool)
	if err != nil {
		return err
	}
	if code != 0 {
		return fmt.Errorf("zpool scrub failed: %s", stderr)
	}
	return nil
}

func (a *Adapter) ScrubStatus(ctx context.Context, pool string) (string, error) {
	out, _, _, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "status", pool)
	if err != nil {
		return "", err
	}
	// Find scan line
	for _, line := range strings.Split(out, "\n") {
		if strings.Contains(line, "scan: ") {
			return strings.TrimSpace(line), nil
		}
	}
	return "no-scan-status", nil
}
