package socket

import (
	"testing"

	"google.golang.org/protobuf/proto"

	altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
	apiContracts "git.toki-labs.com/toki/alt/services/api/internal/contracts"
	"git.toki-labs.com/toki/alt/services/api/internal/workerclient"
	protoSocket "git.toki-labs.com/toki/proto-socket/go"
)

func TestHandleGetLiveBrokerCapabilitiesWorkerForward(t *testing.T) {
	expectedCap := &altv1.LiveBrokerCapability{Broker: "kis"}
	worker := &fakeWorkerClient{
		liveCapRes:  &altv1.GetLiveBrokerCapabilitiesResponse{BrokerCapabilities: expectedCap},
		isConnected: true,
	}

	resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected error: %+v", resp.GetError())
	}
	if resp.GetBrokerCapabilities() == nil {
		t.Fatal("expected broker capabilities, got nil")
	}
	if worker.GetLiveBrokerCapabilitiesCallCount() != 1 {
		t.Errorf("expected worker call count 1, got %d", worker.GetLiveBrokerCapabilitiesCallCount())
	}
	if resp.GetBrokerCapabilities().GetBroker() != "kis" {
		t.Errorf("broker mismatch: %q", resp.GetBrokerCapabilities().GetBroker())
	}
}

func TestHandleGetLiveBrokerCapabilitiesNilWorker(t *testing.T) {
	resp, err := handleGetLiveBrokerCapabilities(nil, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
	if err != nil {
		t.Fatalf("expected typed unavailable response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorUnavailable {
		t.Errorf("expected unavailable error code, got %q", resp.GetError().GetCode())
	}
}

func TestHandleGetLiveBrokerCapabilitiesEmptyAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: ""})
	if err != nil {
		t.Fatalf("expected typed invalid_request response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorInvalidRequest {
		t.Errorf("expected invalid_request error code, got %q", resp.GetError().GetCode())
	}
	if resp.GetError().GetMessage() != "invalid live trading request: account_id is required" {
		t.Errorf("unexpected error message: %q", resp.GetError().GetMessage())
	}
	if worker.GetLiveBrokerCapabilitiesCallCount() != 0 {
		t.Errorf("worker should not be called when account_id is empty, got %d calls", worker.GetLiveBrokerCapabilitiesCallCount())
	}
}

func TestHandleGetLiveBrokerCapabilitiesWorkerUnavailable(t *testing.T) {
	worker := &fakeWorkerClient{connectErr: workerclient.ErrUnavailable, isConnected: false}
	resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
	if err != nil {
		t.Fatalf("expected typed unavailable response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorUnavailable {
		t.Errorf("expected unavailable error code, got %q", resp.GetError().GetCode())
	}
}

func TestHandleGetLiveBrokerCapabilitiesWorkerTimeout(t *testing.T) {
	worker := &fakeWorkerClient{connectErr: workerclient.ErrTimeout, isConnected: false}
	resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
	if err != nil {
		t.Fatalf("expected typed timeout response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorTimeout {
		t.Errorf("expected timeout error code, got %q", resp.GetError().GetCode())
	}
}

// TestHandleGetLiveBrokerCapabilitiesWorkerCallTimeout verifies the live handler
// maps a worker-level call timeout (GetLiveBrokerCapabilities returning
// workerclient.ErrTimeout) to backtestErrorTimeout. This is separate from the
// Connect failure test above because Connect failure path is tested with
// connectErr: workerclient.ErrTimeout above.
func TestHandleGetLiveBrokerCapabilitiesWorkerCallTimeout(t *testing.T) {
	worker := &fakeWorkerClient{err: workerclient.ErrTimeout, isConnected: true}
	resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
	if err != nil {
		t.Fatalf("expected typed timeout response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorTimeout {
		t.Errorf("expected timeout error code, got %q", resp.GetError().GetCode())
	}
	if worker.GetLiveBrokerCapabilitiesCallCount() != 1 {
		t.Errorf("expected worker call count 1, got %d", worker.GetLiveBrokerCapabilitiesCallCount())
	}
}

func TestHandleGetLiveBrokerCapabilitiesNilWorkerResponse(t *testing.T) {
	worker := &fakeWorkerClient{liveCapRes: nil, isConnected: true}
	resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
	if err != nil {
		t.Fatalf("expected typed internal response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorInternal {
		t.Errorf("expected internal error code, got %q", resp.GetError().GetCode())
	}
}

func TestSessionHandlersIncludeLive(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	handlers := sessionHandlers(worker)
	seenLive := false
	for _, h := range handlers {
		if h.requestType == "alt.v1.GetLiveBrokerCapabilitiesRequest" {
			seenLive = true
			break
		}
	}
	if !seenLive {
		t.Error("sessionHandlers should include live capability handler")
	}
}

func TestLiveParserMapContainsLiveMessages(t *testing.T) {
	pm := apiContracts.ParserMap()
	for _, m := range []proto.Message{
		&altv1.GetLiveBrokerCapabilitiesRequest{},
		&altv1.GetLiveBrokerCapabilitiesResponse{},
		&altv1.LiveBrokerCapability{},
	} {
		name := protoSocket.TypeNameOf(m)
		if _, ok := pm[name]; !ok {
			t.Errorf("API parser map missing %q", name)
		}
	}
}
