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{},
		&altv1.SubmitLiveOrderRequest{},
		&altv1.SubmitLiveOrderResponse{},
		&altv1.CancelLiveOrderRequest{},
		&altv1.CancelLiveOrderResponse{},
		&altv1.GetLiveOrderRequest{},
		&altv1.GetLiveOrderResponse{},
		&altv1.GetLiveRiskPolicyRequest{},
		&altv1.GetLiveRiskPolicyResponse{},
		&altv1.LiveRiskPolicy{},
		&altv1.GetLiveKillSwitchRequest{},
		&altv1.GetLiveKillSwitchResponse{},
		&altv1.SetLiveKillSwitchRequest{},
		&altv1.SetLiveKillSwitchResponse{},
		&altv1.LiveKillSwitchState{},
	} {
		name := protoSocket.TypeNameOf(m)
		if _, ok := pm[name]; !ok {
			t.Errorf("API parser map missing %q", name)
		}
	}
}

func TestHandleGetLiveRiskPolicyForward(t *testing.T) {
	worker := &fakeWorkerClient{
		liveRiskPolicyRes: &altv1.GetLiveRiskPolicyResponse{
			Policy: &altv1.LiveRiskPolicy{MaxDailyOrders: 5, MaxOpenOrders: 3, AllowShortSelling: false},
		},
		isConnected: true,
	}
	resp, err := handleGetLiveRiskPolicy(worker, &altv1.GetLiveRiskPolicyRequest{AccountId: "acct-1"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if resp.GetPolicy() == nil {
		t.Fatal("expected policy, got nil")
	}
	if resp.GetPolicy().GetMaxDailyOrders() != 5 {
		t.Errorf("expected max_daily_orders 5, got %d", resp.GetPolicy().GetMaxDailyOrders())
	}
	if worker.liveRiskPolicyReq == nil {
		t.Error("expected worker to be called")
	}
}

func TestHandleGetLiveRiskPolicyMissingAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleGetLiveRiskPolicy(worker, &altv1.GetLiveRiskPolicyRequest{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected typed error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorInvalidRequest {
		t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode())
	}
	if worker.liveRiskPolicyReq != nil {
		t.Error("worker should not be called when account_id is missing")
	}
}

func TestHandleGetLiveKillSwitchForward(t *testing.T) {
	worker := &fakeWorkerClient{
		liveGetKillSwitchRes: &altv1.GetLiveKillSwitchResponse{
			State: &altv1.LiveKillSwitchState{Halted: true, Reason: "kill switch active by default"},
		},
		isConnected: true,
	}
	resp, err := handleGetLiveKillSwitch(worker, &altv1.GetLiveKillSwitchRequest{AccountId: "acct-1"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if resp.GetState() == nil {
		t.Fatal("expected state, got nil")
	}
	if !resp.GetState().GetHalted() {
		t.Error("expected halted=true")
	}
	if worker.liveGetKillSwitchReq == nil {
		t.Error("expected worker to be called")
	}
}

func TestHandleSetLiveKillSwitchForward(t *testing.T) {
	worker := &fakeWorkerClient{
		liveSetKillSwitchRes: &altv1.SetLiveKillSwitchResponse{
			State: &altv1.LiveKillSwitchState{Halted: false, Reason: "operator cleared"},
		},
		isConnected: true,
	}
	resp, err := handleSetLiveKillSwitch(worker, &altv1.SetLiveKillSwitchRequest{
		AccountId: "acct-1",
		Halted:    false,
		Reason:    "operator cleared",
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if resp.GetState() == nil {
		t.Fatal("expected state, got nil")
	}
	if resp.GetState().GetHalted() {
		t.Error("expected halted=false after operator clear")
	}
	if resp.GetState().GetReason() != "operator cleared" {
		t.Errorf("unexpected reason: %q", resp.GetState().GetReason())
	}
	if worker.liveSetKillSwitchReq == nil {
		t.Error("expected worker to be called")
	}
}

func TestHandleSetLiveKillSwitchMissingAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleSetLiveKillSwitch(worker, &altv1.SetLiveKillSwitchRequest{Halted: false})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected typed error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorInvalidRequest {
		t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode())
	}
}

func TestHandleSubmitLiveOrderForward(t *testing.T) {
	worker := &fakeWorkerClient{
		liveSubmitRes: &altv1.SubmitLiveOrderResponse{Order: &altv1.LiveOrder{Id: "lo-1", Status: "submitted"}},
		isConnected:   true,
	}
	resp, err := handleSubmitLiveOrder(worker, &altv1.SubmitLiveOrderRequest{
		AccountId:            "acct-1",
		Intent:               &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "market"},
		OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if resp.GetOrder().GetId() != "lo-1" {
		t.Errorf("unexpected order id: %q", resp.GetOrder().GetId())
	}
	if worker.liveSubmitReq == nil {
		t.Error("expected worker to be called")
	}
}

func TestHandleSubmitLiveOrderMissingAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleSubmitLiveOrder(worker, &altv1.SubmitLiveOrderRequest{
		Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930"},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected typed error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorInvalidRequest {
		t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode())
	}
	if worker.liveSubmitReq != nil {
		t.Error("worker should not be called when account_id is missing")
	}
}

func TestHandleSubmitLiveOrderNilWorker(t *testing.T) {
	resp, err := handleSubmitLiveOrder(nil, &altv1.SubmitLiveOrderRequest{
		AccountId: "acct-1",
		Intent:    &altv1.LiveOrderIntent{InstrumentId: "KRX:005930"},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected typed error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorUnavailable {
		t.Errorf("expected unavailable, got %q", resp.GetError().GetCode())
	}
}

func TestHandleCancelLiveOrderForward(t *testing.T) {
	worker := &fakeWorkerClient{
		liveCancelRes: &altv1.CancelLiveOrderResponse{Order: &altv1.LiveOrder{Id: "lo-1", Status: "canceled"}},
		isConnected:   true,
	}
	resp, err := handleCancelLiveOrder(worker, &altv1.CancelLiveOrderRequest{
		AccountId:   "acct-1",
		LiveOrderId: "lo-1",
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if resp.GetOrder().GetStatus() != "canceled" {
		t.Errorf("unexpected order status: %q", resp.GetOrder().GetStatus())
	}
}

func TestHandleGetLiveOrderForward(t *testing.T) {
	worker := &fakeWorkerClient{
		liveGetRes:  &altv1.GetLiveOrderResponse{Order: &altv1.LiveOrder{Id: "lo-1", Status: "filled", BrokerStatus: "FILLED"}},
		isConnected: true,
	}
	resp, err := handleGetLiveOrder(worker, &altv1.GetLiveOrderRequest{
		AccountId:   "acct-1",
		LiveOrderId: "lo-1",
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if resp.GetOrder().GetBrokerStatus() != "FILLED" {
		t.Errorf("expected broker_status FILLED, got %q", resp.GetOrder().GetBrokerStatus())
	}
}

func TestHandleSyncLiveAccountForward(t *testing.T) {
	snap := &altv1.LiveAccountSnapshot{AccountId: "op-alias-001", Broker: "kis"}
	worker := &fakeWorkerClient{
		liveSyncAccRes: &altv1.SyncLiveAccountResponse{Snapshot: snap},
		isConnected:    true,
	}

	resp, err := handleSyncLiveAccount(worker, &altv1.SyncLiveAccountRequest{AccountId: "op-alias-001"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if worker.liveSyncAccReq == nil {
		t.Fatal("expected request to be forwarded to worker")
	}
	if resp.GetSnapshot().GetAccountId() != "op-alias-001" {
		t.Errorf("account_id mismatch: %q", resp.GetSnapshot().GetAccountId())
	}
}

func TestHandleSyncLiveAccountEmptyAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleSyncLiveAccount(worker, &altv1.SyncLiveAccountRequest{AccountId: ""})
	if err != nil {
		t.Fatalf("expected typed 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, got %q", resp.GetError().GetCode())
	}
	if worker.liveSyncAccReq != nil {
		t.Error("worker should not be called when account_id is empty")
	}
}

func TestHandleSyncLiveAccountNilWorker(t *testing.T) {
	resp, err := handleSyncLiveAccount(nil, &altv1.SyncLiveAccountRequest{AccountId: "op-alias-001"})
	if err != nil {
		t.Fatalf("expected typed response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorUnavailable {
		t.Errorf("expected unavailable, got %q", resp.GetError().GetCode())
	}
}

func TestHandleGetLiveAccountSnapshotForward(t *testing.T) {
	snap := &altv1.LiveAccountSnapshot{AccountId: "op-alias-001", Broker: "kis", Stale: false}
	worker := &fakeWorkerClient{
		liveGetAccSnapRes: &altv1.GetLiveAccountSnapshotResponse{Snapshot: snap},
		isConnected:       true,
	}

	resp, err := handleGetLiveAccountSnapshot(worker, &altv1.GetLiveAccountSnapshotRequest{AccountId: "op-alias-001"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if worker.liveGetAccSnapReq == nil {
		t.Fatal("expected request to be forwarded to worker")
	}
	if resp.GetSnapshot().GetAccountId() != "op-alias-001" {
		t.Errorf("account_id mismatch: %q", resp.GetSnapshot().GetAccountId())
	}
}

func TestHandleGetLiveAccountSnapshotEmptyAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleGetLiveAccountSnapshot(worker, &altv1.GetLiveAccountSnapshotRequest{AccountId: ""})
	if err != nil {
		t.Fatalf("expected typed 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, got %q", resp.GetError().GetCode())
	}
}

func TestHandleGetLiveAccountSnapshotNilWorker(t *testing.T) {
	resp, err := handleGetLiveAccountSnapshot(nil, &altv1.GetLiveAccountSnapshotRequest{AccountId: "op-alias-001"})
	if err != nil {
		t.Fatalf("expected typed response, got error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorUnavailable {
		t.Errorf("expected unavailable, got %q", resp.GetError().GetCode())
	}
}

func TestApiLiveHandlersRegistrationIncludesAccountSync(t *testing.T) {
	required := map[string]bool{
		"alt.v1.SyncLiveAccountRequest":        false,
		"alt.v1.GetLiveAccountSnapshotRequest": false,
	}
	for _, h := range apiLiveHandlers(nil) {
		if _, ok := required[h.requestType]; ok {
			required[h.requestType] = true
		}
	}
	for name, seen := range required {
		if !seen {
			t.Errorf("missing API live handler registration: %s", name)
		}
	}
}

func TestApiLiveHandlersRegistrationIncludesAuditQuery(t *testing.T) {
	found := false
	for _, h := range apiLiveHandlers(nil) {
		if h.requestType == "alt.v1.ListLiveAuditEventsRequest" {
			found = true
			break
		}
	}
	if !found {
		t.Error("missing API live handler registration: alt.v1.ListLiveAuditEventsRequest")
	}
}

func TestHandleListLiveAuditEventsForward(t *testing.T) {
	auditEvents := []*altv1.LiveAuditEvent{
		{EventId: "aud-1", Type: "submit_confirmed", AccountId: "acct-1", OrderId: "lo-1"},
	}
	worker := &fakeWorkerClient{
		liveListAuditRes: &altv1.ListLiveAuditEventsResponse{Events: auditEvents},
		isConnected:      true,
	}
	resp, err := handleListLiveAuditEvents(worker, &altv1.ListLiveAuditEventsRequest{AccountId: "acct-1"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() != nil {
		t.Fatalf("unexpected typed error: %+v", resp.GetError())
	}
	if worker.liveListAuditReq == nil {
		t.Fatal("expected request to be forwarded to worker")
	}
	if len(resp.GetEvents()) != 1 {
		t.Fatalf("expected 1 event, got %d", len(resp.GetEvents()))
	}
	if resp.GetEvents()[0].GetEventId() != "aud-1" {
		t.Errorf("event_id mismatch: %q", resp.GetEvents()[0].GetEventId())
	}
}

func TestHandleListLiveAuditEventsEmptyAccountID(t *testing.T) {
	worker := &fakeWorkerClient{isConnected: true}
	resp, err := handleListLiveAuditEvents(worker, &altv1.ListLiveAuditEventsRequest{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected typed error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorInvalidRequest {
		t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode())
	}
	if worker.liveListAuditReq != nil {
		t.Error("worker should not be called when account_id is empty")
	}
}

func TestHandleListLiveAuditEventsNilWorker(t *testing.T) {
	resp, err := handleListLiveAuditEvents(nil, &altv1.ListLiveAuditEventsRequest{AccountId: "acct-1"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if resp.GetError() == nil {
		t.Fatal("expected typed error, got nil")
	}
	if resp.GetError().GetCode() != backtestErrorUnavailable {
		t.Errorf("expected unavailable, got %q", resp.GetError().GetCode())
	}
}
