package proxmox

import (
	"context"
	"errors"
	"net/http"
	"strings"
	"testing"
	"time"
)

const testUPID = "UPID:demo-felhom:00026454:004E3431:6A265E53:vzsnapshot:9001:root@pam:"

// fastWait keeps tests quick.
var fastWait = WaitOptions{Interval: time.Millisecond, MaxInterval: 2 * time.Millisecond, Timeout: time.Second}

func TestWaitTask_RunningThenOK(t *testing.T) {
	var n int
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		n++
		if n == 1 {
			return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"running"}}`), nil
		}
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"stopped","exitstatus":"OK"}}`), nil
	}}
	st, err := newTestClient(d).WaitTask(context.Background(), testUPID, fastWait)
	if err != nil {
		t.Fatalf("WaitTask: %v", err)
	}
	if !st.OK() {
		t.Errorf("status not OK: %+v", st)
	}
}

func TestWaitTask_FailedSurfacesPrivilege(t *testing.T) {
	// vzdump against an unauthorized vmid: 200+UPID, then the 403 in exitstatus.
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		if strings.Contains(r.URL.Path, "/log") {
			return jsonResp(200, `{"data":[{"n":1,"t":"TASK ERROR: 403 Permission check failed (/vms/9000, VM.Backup)"}]}`), nil
		}
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"stopped","exitstatus":"403 Permission check failed (/vms/9000, VM.Backup)"}}`), nil
	}}
	_, err := newTestClient(d).WaitTask(context.Background(), testUPID, fastWait)
	var te *TaskError
	if !errors.As(err, &te) {
		t.Fatalf("want *TaskError, got %T: %v", err, err)
	}
	if te.Privilege != "VM.Backup" {
		t.Errorf("privilege = %q, want VM.Backup", te.Privilege)
	}
	if te.DeniedPath != "/vms/9000" {
		t.Errorf("denied path = %q", te.DeniedPath)
	}
	if len(te.LogTail) == 0 {
		t.Errorf("expected a log tail")
	}
}

func TestWaitTask_AllowWarnings_Accepts(t *testing.T) {
	// A start task that completes with the systemd-nesting advisory exits "WARNINGS: 1".
	// With AllowWarnings, that's success and ExitStatus is returned intact for the caller.
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"stopped","exitstatus":"WARNINGS: 1"}}`), nil
	}}
	opts := fastWait
	opts.AllowWarnings = true
	st, err := newTestClient(d).WaitTask(context.Background(), testUPID, opts)
	if err != nil {
		t.Fatalf("AllowWarnings should accept WARNINGS: %v", err)
	}
	if st.ExitStatus != "WARNINGS: 1" {
		t.Errorf("ExitStatus = %q, want %q (must be returned intact so the caller can read it)", st.ExitStatus, "WARNINGS: 1")
	}
}

func TestWaitTask_AllowWarnings_RealErrorStillFails(t *testing.T) {
	// AllowWarnings must NOT swallow a genuine non-WARNINGS failure.
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		if strings.Contains(r.URL.Path, "/log") {
			return jsonResp(200, `{"data":[{"n":1,"t":"TASK ERROR: 403 Permission check failed (/vms/9000, VM.PowerMgmt)"}]}`), nil
		}
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"stopped","exitstatus":"403 Permission check failed (/vms/9000, VM.PowerMgmt)"}}`), nil
	}}
	opts := fastWait
	opts.AllowWarnings = true
	_, err := newTestClient(d).WaitTask(context.Background(), testUPID, opts)
	var te *TaskError
	if !errors.As(err, &te) {
		t.Fatalf("a real error must still be *TaskError even with AllowWarnings, got %T: %v", err, err)
	}
}

func TestWaitTask_DefaultRejectsWarnings(t *testing.T) {
	// Default (AllowWarnings:false) keeps every existing caller strict: WARNINGS → *TaskError.
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		if strings.Contains(r.URL.Path, "/log") {
			return jsonResp(200, `{"data":[{"n":1,"t":"WARN: Systemd 257 detected. You may need to enable nesting."}]}`), nil
		}
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"stopped","exitstatus":"WARNINGS: 1"}}`), nil
	}}
	_, err := newTestClient(d).WaitTask(context.Background(), testUPID, fastWait) // AllowWarnings:false
	var te *TaskError
	if !errors.As(err, &te) {
		t.Fatalf("default must still fail on WARNINGS (existing callers unaffected), got %T: %v", err, err)
	}
}

func TestWaitTask_Timeout(t *testing.T) {
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"running"}}`), nil
	}}
	opts := WaitOptions{Interval: time.Millisecond, MaxInterval: time.Millisecond, Timeout: 30 * time.Millisecond}
	_, err := newTestClient(d).WaitTask(context.Background(), testUPID, opts)
	if err == nil || !errors.Is(err, context.DeadlineExceeded) {
		t.Fatalf("want deadline-exceeded, got %v", err)
	}
}

func TestWaitTask_CtxCancel(t *testing.T) {
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		return jsonResp(200, `{"data":{"upid":"`+testUPID+`","status":"running"}}`), nil
	}}
	ctx, cancel := context.WithCancel(context.Background())
	go func() { time.Sleep(20 * time.Millisecond); cancel() }()
	opts := WaitOptions{Interval: time.Millisecond, MaxInterval: time.Millisecond, Timeout: time.Minute}
	_, err := newTestClient(d).WaitTask(ctx, testUPID, opts)
	if err == nil || !errors.Is(err, context.Canceled) {
		t.Fatalf("want canceled, got %v", err)
	}
}
