package job

import (
	"context"
	"database/sql"
	"encoding/json"
	"log"
	"time"

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

type Runner struct {
	DB    *sql.DB
	ZFS   *zfs.Adapter
	Audit audit.AuditLogger
}

func (r *Runner) Enqueue(ctx context.Context, j domain.Job) (string, error) {
	id := uuid.New().String()
	if j.ID == "" {
		j.ID = domain.UUID(id)
	}
	j.Status = "queued"
	j.CreatedAt = time.Now()
	j.UpdatedAt = time.Now()
	// persist details JSON if present
	detailsJSON := ""
	if j.Details != nil {
		b, _ := json.Marshal(j.Details)
		detailsJSON = string(b)
	}
	_, err := r.DB.ExecContext(ctx, `INSERT INTO jobs (id, type, status, progress, owner, created_at, updated_at, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
		j.ID, j.Type, j.Status, j.Progress, j.Owner, j.CreatedAt, j.UpdatedAt, detailsJSON)
	if err != nil {
		return "", err
	}
	log.Printf("enqueued job %s (%s)", j.ID, j.Type)
	// run async worker (very simple worker for skeleton)
	go func() {
		// update running
		_ = r.updateStatus(ctx, j.ID, "running", 0)
		// execute based on job type
		switch j.Type {
		case "create-pool":
			// parse details: expect name and vdevs
			var name string
			var vdevs []string
			if j.Details != nil {
				if n, ok := j.Details["name"].(string); ok {
					name = n
				}
				if rawV, ok := j.Details["vdevs"].([]any); ok {
					for _, vv := range rawV {
						if s, ok := vv.(string); ok {
							vdevs = append(vdevs, s)
						}
					}
				}
			}
			_ = r.updateStatus(ctx, j.ID, "running", 10)
			if r.ZFS != nil {
				// call sync create pool
				if err := r.ZFS.CreatePoolSync(ctx, name, vdevs); err != nil {
					_ = r.updateStatus(ctx, j.ID, "failed", 0)
					if r.Audit != nil {
						r.Audit.Record(ctx, audit.Event{UserID: string(j.Owner), Action: "pool.create", ResourceType: "pool", ResourceID: name, Success: false, Details: map[string]any{"error": err.Error()}})
					}
					return
				}
				_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
				if r.Audit != nil {
					r.Audit.Record(ctx, audit.Event{UserID: string(j.Owner), Action: "pool.create", ResourceType: "pool", ResourceID: name, Success: true})
				}
				return
			}
			_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
		case "snapshot":
			_ = r.updateStatus(ctx, j.ID, "running", 10)
			// call zfs snapshot; expect dataset and name
			var dataset, snapName string
			if j.Details != nil {
				if d, ok := j.Details["dataset"].(string); ok {
					dataset = d
				}
				if s, ok := j.Details["snap_name"].(string); ok {
					snapName = s
				}
			}
			if r.ZFS != nil {
				if err := r.ZFS.Snapshot(ctx, dataset, snapName); err != nil {
					_ = r.updateStatus(ctx, j.ID, "failed", 0)
					if r.Audit != nil {
						r.Audit.Record(ctx, audit.Event{UserID: string(j.Owner), Action: "snapshot", ResourceType: "snapshot", ResourceID: dataset + "@" + snapName, Success: false, Details: map[string]any{"error": err.Error()}})
					}
					return
				}
				_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
				if r.Audit != nil {
					r.Audit.Record(ctx, audit.Event{UserID: string(j.Owner), Action: "snapshot", ResourceType: "snapshot", ResourceID: dataset + "@" + snapName, Success: true})
				}
				return
			}
			_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
		case "scrub":
			_ = r.updateStatus(ctx, j.ID, "running", 10)
			var pool string
			if j.Details != nil {
				if p, ok := j.Details["pool"].(string); ok {
					pool = p
				}
			}
			if r.ZFS != nil {
				if err := r.ZFS.ScrubStart(ctx, pool); err != nil {
					_ = r.updateStatus(ctx, j.ID, "failed", 0)
					if r.Audit != nil {
						r.Audit.Record(ctx, audit.Event{UserID: string(j.Owner), Action: "pool.scrub", ResourceType: "pool", ResourceID: pool, Success: false, Details: map[string]any{"error": err.Error()}})
					}
					return
				}
				_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
				if r.Audit != nil {
					r.Audit.Record(ctx, audit.Event{UserID: string(j.Owner), Action: "pool.scrub", ResourceType: "pool", ResourceID: pool, Success: true})
				}
				return
			}
			_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
		default:
			// unknown job types just succeed
			time.Sleep(500 * time.Millisecond)
			_ = r.updateStatus(ctx, j.ID, "succeeded", 100)
		}
	}()
	return id, nil
}

func (r *Runner) updateStatus(ctx context.Context, id domain.UUID, status string, progress int) error {
	_, err := r.DB.ExecContext(ctx, `UPDATE jobs SET status = ?, progress = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, status, progress, id)
	return err
}
