package storage

import (
	"context"
	"errors"
	"fmt"

	"github.com/example/storage-appliance/internal/audit"
	"github.com/example/storage-appliance/internal/domain"
	"github.com/example/storage-appliance/internal/infra/zfs"
	"github.com/example/storage-appliance/internal/service"
)

var (
	ErrForbidden     = errors.New("forbidden")
	ErrDuplicatePool = errors.New("duplicate pool name")
)

type StorageService struct {
	ZFS       zfs.Adapter
	JobRunner service.JobRunner
	Audit     audit.AuditLogger
}

func NewStorageService(z *zfs.Adapter, jr service.JobRunner, al audit.AuditLogger) *StorageService {
	return &StorageService{ZFS: *z, JobRunner: jr, Audit: al}
}

// ListPools returns pools via zfs adapter
func (s *StorageService) ListPools(ctx context.Context) ([]domain.Pool, error) {
	return s.ZFS.ListPools(ctx)
}

// CreatePool validates and enqueues a create pool job; user must be admin
func (s *StorageService) CreatePool(ctx context.Context, user string, role string, name string, vdevs []string) (string, error) {
	if role != "admin" {
		return "", ErrForbidden
	}
	// Simple validation: new name not in existing pools
	pools, err := s.ZFS.ListPools(ctx)
	if err != nil {
		return "", err
	}
	for _, p := range pools {
		if p.Name == name {
			return "", ErrDuplicatePool
		}
	}
	// Create a job to build a pool. For skeleton, we just create a job entry with type create-pool
	j := domain.Job{Type: "create-pool", Status: "queued", Owner: domain.UUID(user)}
	j.Details = map[string]any{"name": name, "vdevs": vdevs}
	id, err := s.JobRunner.Enqueue(ctx, j)
	// Store details in audit
	if s.Audit != nil {
		s.Audit.Record(ctx, audit.Event{UserID: user, Action: "pool.create.request", ResourceType: "pool", ResourceID: name, Success: err == nil, Details: map[string]any{"vdevs": vdevs}})
	}
	return id, err
}

// Snapshot dataset
func (s *StorageService) Snapshot(ctx context.Context, user, role, dataset, snapName string) (string, error) {
	if role != "admin" && role != "operator" {
		return "", ErrForbidden
	}
	// call zfs snapshot, but do as job; enqueue
	j := domain.Job{Type: "snapshot", Status: "queued", Owner: domain.UUID(user)}
	j.Details = map[string]any{"dataset": dataset, "snap_name": snapName}
	id, err := s.JobRunner.Enqueue(ctx, j)
	if s.Audit != nil {
		s.Audit.Record(ctx, audit.Event{UserID: user, Action: "dataset.snapshot.request", ResourceType: "snapshot", ResourceID: fmt.Sprintf("%s@%s", dataset, snapName), Success: err == nil, Details: map[string]any{"dataset": dataset}})
	}
	return id, err
}

func (s *StorageService) ScrubStart(ctx context.Context, user, role, pool string) (string, error) {
	if role != "admin" && role != "operator" {
		return "", ErrForbidden
	}
	j := domain.Job{Type: "scrub", Status: "queued", Owner: domain.UUID(user)}
	j.Details = map[string]any{"pool": pool}
	id, err := s.JobRunner.Enqueue(ctx, j)
	if s.Audit != nil {
		s.Audit.Record(ctx, audit.Event{UserID: user, Action: "pool.scrub.request", ResourceType: "pool", ResourceID: pool, Success: err == nil})
	}
	return id, err
}

// ListDatasets returns datasets for a pool
func (s *StorageService) ListDatasets(ctx context.Context, pool string) ([]domain.Dataset, error) {
	return s.ZFS.ListDatasets(ctx, pool)
}

// CreateDataset creates dataset synchronously or as job; for skeleton, do sync
func (s *StorageService) CreateDataset(ctx context.Context, user, role, name string, props map[string]string) error {
	if role != "admin" && role != "operator" {
		return ErrForbidden
	}
	err := s.ZFS.CreateDataset(ctx, name, props)
	if s.Audit != nil {
		s.Audit.Record(ctx, audit.Event{UserID: user, Action: "dataset.create", ResourceType: "dataset", ResourceID: name, Success: err == nil, Details: map[string]any{"props": props}})
	}
	return err
}

// GetPoolStatus calls the adapter
func (s *StorageService) GetPoolStatus(ctx context.Context, pool string) (domain.PoolHealth, error) {
	return s.ZFS.GetPoolStatus(ctx, pool)
}
