package jobs

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"testing"
)

func TestRunnerDispatchesRegisteredHandler(t *testing.T) {
	runner := NewRunner()
	called := false
	var receivedPayload json.RawMessage

	testKind := Kind("test_job")
	runner.Register(testKind, func(ctx context.Context, payload json.RawMessage) error {
		called = true
		receivedPayload = payload
		return nil
	})

	job := Job{
		ID:      "job-1",
		Kind:    testKind,
		Payload: json.RawMessage(`{"key":"value"}`),
	}

	err := runner.Execute(context.Background(), job)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if !called {
		t.Error("expected handler to be called, but it was not")
	}

	if string(receivedPayload) != `{"key":"value"}` {
		t.Errorf("expected payload %q, got %q", `{"key":"value"}`, string(receivedPayload))
	}
}

func TestRunnerRejectsUnknownKind(t *testing.T) {
	runner := NewRunner()
	job := Job{
		ID:   "job-2",
		Kind: Kind("non_existent"),
	}

	err := runner.Execute(context.Background(), job)
	if err == nil {
		t.Fatal("expected error for unknown job kind, got nil")
	}
}

func TestRunnerRespectsCanceledContext(t *testing.T) {
	runner := NewRunner()
	testKind := Kind("cancellation_test")

	runner.Register(testKind, func(ctx context.Context, payload json.RawMessage) error {
		return nil
	})

	ctx, cancel := context.WithCancel(context.Background())
	cancel() // Cancel context immediately

	job := Job{
		ID:   "job-3",
		Kind: testKind,
	}

	err := runner.Execute(ctx, job)
	if err == nil {
		t.Fatal("expected error due to canceled context, got nil")
	}
	if !errors.Is(err, context.Canceled) {
		t.Errorf("expected error %v, got %v", context.Canceled, err)
	}
}

func TestRunnerHandlesPanic(t *testing.T) {
	runner := NewRunner()
	panicKind := Kind("panic_job")

	runner.Register(panicKind, func(ctx context.Context, payload json.RawMessage) error {
		panic("something went critically wrong")
	})

	job := Job{
		ID:   "job-4",
		Kind: panicKind,
	}

	err := runner.Execute(context.Background(), job)
	if err == nil {
		t.Fatal("expected error from panic recovery, got nil")
	}
	if err.Error() != "job panicked: something went critically wrong" {
		t.Errorf("unexpected error message: %v", err)
	}
}

func TestRegisterBuiltins(t *testing.T) {
	runner := NewRunner()
	RegisterBuiltins(runner)

	expectedCount := 3
	if runner.Len() != expectedCount {
		t.Errorf("expected %d registered built-in handlers, got %d", expectedCount, runner.Len())
	}
}

// --- ExecuteWithResult tests ---

func TestRunnerExecuteWithResultSuccess(t *testing.T) {
	runner := NewRunner()
	testKind := Kind("ewr_success")

	var payload json.RawMessage
	runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
		payload = p
		return nil
	})
	runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
		return Result{Bars: 42, Instruments: 3}, nil
	})

	job := Job{
		ID:      "ewr-1",
		Kind:    testKind,
		Payload: json.RawMessage(`{"key":"value"}`),
	}

	res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
	if err != nil {
		t.Fatalf("ExecuteWithResult: unexpected error: %v", err)
	}
	if !hasResult {
		t.Fatal("ExecuteWithResult: expected hasResult=true, got false")
	}
	if res.Bars != 42 || res.Instruments != 3 {
		t.Errorf("ExecuteWithResult: expected {Bars:42,Instruments:3}, got %+v", res)
	}
	// Verify the handler was called with the correct payload.
	_ = payload // payload is captured by the Register handler; assertion is above.
}

func TestRunnerExecuteWithResultMissingHandler(t *testing.T) {
	runner := NewRunner()
	testKind := Kind("ewr_missing")

	// Only register Handler, not WithResultHandler
	runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
		return nil
	})

	job := Job{
		ID:   "ewr-2",
		Kind: testKind,
	}

	res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
	if err != nil {
		t.Fatalf("ExecuteWithResult: expected no error for missing handler, got: %v", err)
	}
	if hasResult {
		t.Fatal("ExecuteWithResult: expected hasResult=false for missing handler, got true")
	}
	if res.Bars != 0 {
		t.Errorf("ExecuteWithResult: expected zero result for missing handler, got %+v", res)
	}
}

func TestRunnerExecuteWithResultHandlerError(t *testing.T) {
	runner := NewRunner()
	testKind := Kind("ewr_error")
	wantErr := fmt.Errorf("import failed: connection reset")

	runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
		return nil
	})
	runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
		return Result{}, wantErr
	})

	job := Job{
		ID:   "ewr-3",
		Kind: testKind,
	}

	res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
	if err == nil {
		t.Fatal("ExecuteWithResult: expected error from handler, got nil")
	}
	if err != wantErr {
		t.Errorf("ExecuteWithResult: expected error %v, got %v", wantErr, err)
	}
	if hasResult {
		t.Fatal("ExecuteWithResult: expected hasResult=false on error, got true")
	}
	if res.Bars != 0 {
		t.Errorf("ExecuteWithResult: expected zero result on error, got %+v", res)
	}
}

func TestRunnerExecuteWithResultCanceledContext(t *testing.T) {
	runner := NewRunner()
	testKind := Kind("ewr_cancel")

	runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
		return nil
	})
	runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
		return Result{Bars: 10}, nil
	})

	ctx, cancel := context.WithCancel(context.Background())
	cancel() // Cancel immediately

	job := Job{
		ID:   "ewr-4",
		Kind: testKind,
	}

	res, hasResult, err := runner.ExecuteWithResult(ctx, job)
	if err == nil {
		t.Fatal("ExecuteWithResult: expected context.Canceled error, got nil")
	}
	if !errors.Is(err, context.Canceled) {
		t.Errorf("ExecuteWithResult: expected context.Canceled, got %v", err)
	}
	if hasResult {
		t.Fatal("ExecuteWithResult: expected hasResult=false on cancel, got true")
	}
	if res.Bars != 0 {
		t.Errorf("ExecuteWithResult: expected zero result on cancel, got %+v", res)
	}
}

func TestRunnerExecuteWithResultPanic(t *testing.T) {
	runner := NewRunner()
	testKind := Kind("ewr_panic")

	runner.Register(testKind, func(ctx context.Context, p json.RawMessage) error {
		return nil
	})
	runner.RegisterWithResult(testKind, func(ctx context.Context, p json.RawMessage) (Result, error) {
		panic("boom in handler")
	})

	job := Job{
		ID:   "ewr-5",
		Kind: testKind,
	}

	res, hasResult, err := runner.ExecuteWithResult(context.Background(), job)
	if err == nil {
		t.Fatal("ExecuteWithResult: expected panic error, got nil")
	}
	if !strings.Contains(err.Error(), "panicked") {
		t.Errorf("ExecuteWithResult: expected 'panicked' in error message, got: %v", err)
	}
	if hasResult {
		t.Fatal("ExecuteWithResult: expected hasResult=false on panic, got true")
	}
	if res.Bars != 0 {
		t.Errorf("ExecuteWithResult: expected zero result on panic, got %+v", res)
	}
}
