package livetrading

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"testing"
	"time"

	"git.toki-labs.com/toki/alt/packages/domain/market"
	"git.toki-labs.com/toki/alt/packages/domain/trading"
)

// fakeBroker implements BrokerPort for testing and records every call so tests
// can assert call counts and the exact intent that reached the broker.
type fakeBroker struct {
	submitResult  OrderResult
	submitErr     error
	cancelResult  OrderResult
	cancelErr     error
	statusResult  OrderResult
	statusErr     error
	caps          trading.BrokerCapability
	capsErr       error
	snapshot      trading.AccountSnapshot
	snapshotErr   error
	submitCalls   int
	lastAccountID string
	lastIntent    trading.OrderIntent
}

func (f *fakeBroker) GetCapabilities(_ context.Context, _ string) (trading.BrokerCapability, error) {
	return f.caps, f.capsErr
}

func (f *fakeBroker) SubmitOrder(_ context.Context, accountID string, intent trading.OrderIntent) (OrderResult, error) {
	f.submitCalls++
	f.lastAccountID = accountID
	f.lastIntent = intent
	return f.submitResult, f.submitErr
}

func (f *fakeBroker) CancelOrder(_ context.Context, _, _ string) (OrderResult, error) {
	return f.cancelResult, f.cancelErr
}

func (f *fakeBroker) GetOrderStatus(_ context.Context, _, _ string) (OrderResult, error) {
	return f.statusResult, f.statusErr
}

func (f *fakeBroker) FetchAccountSnapshot(_ context.Context, _ string) (trading.AccountSnapshot, error) {
	return f.snapshot, f.snapshotErr
}

func confirmedReq(accountID, instrumentID, orderType string) SubmitRequest {
	return SubmitRequest{
		AccountID: accountID,
		Intent: trading.OrderIntent{
			InstrumentID: market.InstrumentID(instrumentID),
			Side:         trading.OrderSideBuy,
			Quantity:     market.Quantity{Amount: market.Decimal{Value: "100"}},
			Type:         trading.OrderType(orderType),
		},
		Confirmation: OperatorConfirmation{
			Confirmed:  true,
			OperatorID: "test-op",
			Reason:     "test submit",
		},
	}
}

func TestSubmitLiveOrderRequiresOperatorConfirmation(t *testing.T) {
	svc := NewService(&fakeBroker{})
	req := confirmedReq("acct-1", "KRX:005930", "market")
	req.Confirmation.Confirmed = false

	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if !errors.Is(err, ErrConfirmationRequired) {
		t.Fatalf("expected ErrConfirmationRequired, got %v", err)
	}
}

func TestSubmitLiveOrderPreservesCustomOrderType(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{
			BrokerOrderID: "broker-001",
			Status:        trading.OrderStatusSubmitted,
			BrokerStatus:  "ACCEPTED",
		},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	req := confirmedReq("acct-1", "KRX:005930", "kis_best_limit")

	order, err := svc.SubmitLiveOrder(context.Background(), req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if string(order.Intent.Type) != "kis_best_limit" {
		t.Errorf("order type not preserved: %q", order.Intent.Type)
	}
}

func TestSubmitLiveOrderCallsBrokerOnlyAfterConfirmation(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	// Unconfirmed: broker must NOT be called.
	req := confirmedReq("acct-1", "KRX:005930", "market")
	req.Confirmation.Confirmed = false
	_, _ = svc.SubmitLiveOrder(context.Background(), req)
	if broker.submitCalls != 0 {
		t.Fatalf("broker should not be called without confirmation, got %d calls", broker.submitCalls)
	}

	// Confirmed: broker must be called exactly once with the full intent.
	req.Confirmation.Confirmed = true
	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if broker.submitCalls != 1 {
		t.Fatalf("expected exactly 1 broker call after confirmation, got %d", broker.submitCalls)
	}
	if broker.lastAccountID != "acct-1" {
		t.Errorf("broker received account_id = %q, want acct-1", broker.lastAccountID)
	}
	if string(broker.lastIntent.Type) != "market" {
		t.Errorf("broker received order type = %q, want market", broker.lastIntent.Type)
	}
}

func TestSubmitLiveOrderMalformedIntentDoesNotReachBroker(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)

	cases := []struct {
		name string
		req  SubmitRequest
	}{
		{
			name: "missing instrument_id",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					Side:     trading.OrderSideBuy,
					Quantity: market.Quantity{Amount: market.Decimal{Value: "1"}},
					Type:     trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "invalid side",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSide("long"),
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "1"}},
					Type:         trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "missing quantity",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Type:         trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "missing order type",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "1"}},
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "empty account_id",
			req: SubmitRequest{
				AccountID: "",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "1"}},
					Type:         trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "zero quantity (0.0)",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "0.0"}},
					Type:         trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "negative quantity",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "-1"}},
					Type:         trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "nonnumeric quantity",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "abc"}},
					Type:         trading.OrderType("market"),
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
		{
			name: "negative limit price",
			req: SubmitRequest{
				AccountID: "acct-1",
				Intent: trading.OrderIntent{
					InstrumentID: market.InstrumentID("KRX:005930"),
					Side:         trading.OrderSideBuy,
					Quantity:     market.Quantity{Amount: market.Decimal{Value: "10"}},
					Type:         trading.OrderType("limit"),
					LimitPrice:   market.Price{Amount: market.Decimal{Value: "-100"}},
				},
				Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			broker.submitCalls = 0
			_, err := svc.SubmitLiveOrder(context.Background(), tc.req)
			if !errors.Is(err, ErrInvalidIntent) {
				t.Errorf("expected ErrInvalidIntent, got %v", err)
			}
			if broker.submitCalls != 0 {
				t.Errorf("broker should not be called for malformed intent, got %d calls", broker.submitCalls)
			}
		})
	}
}

func TestSubmitLiveOrderBrokerUnavailable(t *testing.T) {
	svc := NewService(nil)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	req := confirmedReq("acct-1", "KRX:005930", "market")

	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if !errors.Is(err, ErrBrokerUnavailable) {
		t.Fatalf("expected ErrBrokerUnavailable, got %v", err)
	}
}

func TestCancelLiveOrderTransitionsStatus(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "broker-001", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
		cancelResult: OrderResult{BrokerOrderID: "broker-001", Status: trading.OrderStatusCanceled, BrokerStatus: "CANCELED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	order, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "limit"))
	if err != nil {
		t.Fatalf("submit error: %v", err)
	}

	canceled, err := svc.CancelLiveOrder(context.Background(), "acct-1", order.ID)
	if err != nil {
		t.Fatalf("cancel error: %v", err)
	}
	if canceled.Status != trading.OrderStatusCanceled {
		t.Errorf("expected status %q, got %q", trading.OrderStatusCanceled, canceled.Status)
	}
	if string(canceled.BrokerStatus) != "CANCELED" {
		t.Errorf("expected broker_status CANCELED, got %q", canceled.BrokerStatus)
	}
}

func TestCancelLiveOrderNotFound(t *testing.T) {
	svc := NewService(&fakeBroker{})

	_, err := svc.CancelLiveOrder(context.Background(), "acct-1", "nonexistent")
	if !errors.Is(err, ErrOrderNotFound) {
		t.Fatalf("expected ErrOrderNotFound, got %v", err)
	}
}

func TestGetLiveOrderReturnsBrokerStatus(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "broker-001", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
		statusResult: OrderResult{BrokerOrderID: "broker-001", Status: trading.OrderStatusFilled, BrokerStatus: "FILLED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	order, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
	if err != nil {
		t.Fatalf("submit error: %v", err)
	}

	got, err := svc.GetLiveOrder(context.Background(), "acct-1", order.ID)
	if err != nil {
		t.Fatalf("get error: %v", err)
	}
	if got.Status != trading.OrderStatusFilled {
		t.Errorf("expected status %q, got %q", trading.OrderStatusFilled, got.Status)
	}
	if string(got.BrokerStatus) != "FILLED" {
		t.Errorf("expected broker_status FILLED, got %q", got.BrokerStatus)
	}
}

func TestGetLiveBrokerCapabilitiesUnavailable(t *testing.T) {
	svc := NewService(nil)
	_, err := svc.GetLiveBrokerCapabilities(context.Background(), "acct-1")
	if !errors.Is(err, ErrBrokerUnavailable) {
		t.Fatalf("expected ErrBrokerUnavailable, got %v", err)
	}
}

func TestSubmitLiveOrderKillSwitchBlocksBeforeBroker(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: "submitted", BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	// NewService starts with kill switch halted=true.

	req := confirmedReq("acct-1", "KRX:005930", "market")
	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if !errors.Is(err, ErrRiskBlocked) {
		t.Fatalf("expected ErrRiskBlocked when kill switch is halted, got %v", err)
	}
	if broker.submitCalls != 0 {
		t.Errorf("broker must not be called when kill switch is halted, got %d calls", broker.submitCalls)
	}
}

func TestSubmitLiveOrderMaxNotionalBlocks(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: "submitted", BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	// 20 * 75000 = 1500000 > 1000000 (default KRW limit)
	req := SubmitRequest{
		AccountID: "acct-1",
		Intent: trading.OrderIntent{
			InstrumentID: market.InstrumentID("KRX:005930"),
			Side:         trading.OrderSideBuy,
			Quantity:     market.Quantity{Amount: market.Decimal{Value: "20"}},
			Type:         trading.OrderType("limit"),
			LimitPrice:   market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "75000"}},
		},
		Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
	}
	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if !errors.Is(err, ErrRiskBlocked) {
		t.Fatalf("expected ErrRiskBlocked for excessive notional, got %v", err)
	}
	if broker.submitCalls != 0 {
		t.Errorf("broker must not be called when notional exceeds limit, got %d calls", broker.submitCalls)
	}
}

func TestSubmitLiveOrderRiskAllowedCallsBroker(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: "submitted", BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	// 1 * 75000 = 75000 < 1000000
	req := SubmitRequest{
		AccountID: "acct-1",
		Intent: trading.OrderIntent{
			InstrumentID: market.InstrumentID("KRX:005930"),
			Side:         trading.OrderSideBuy,
			Quantity:     market.Quantity{Amount: market.Decimal{Value: "1"}},
			Type:         trading.OrderType("limit"),
			LimitPrice:   market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "75000"}},
		},
		Confirmation: OperatorConfirmation{Confirmed: true, OperatorID: "op-1"},
	}
	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if err != nil {
		t.Fatalf("expected success for allowed order, got %v", err)
	}
	if broker.submitCalls != 1 {
		t.Errorf("expected exactly 1 broker call, got %d", broker.submitCalls)
	}
}

func TestSetAndGetKillSwitch(t *testing.T) {
	svc := NewService(nil)
	got := svc.GetKillSwitch()
	if !got.Halted {
		t.Error("default kill switch must be halted")
	}

	svc.SetKillSwitch(trading.KillSwitchState{Halted: false, Reason: "test"})
	got = svc.GetKillSwitch()
	if got.Halted {
		t.Error("expected kill switch unhalted after SetKillSwitch")
	}
	if got.Reason != "test" {
		t.Errorf("expected reason %q, got %q", "test", got.Reason)
	}
}

func TestSubmitLiveOrderMaxDailyOrdersBlocksBeforeBroker(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	// Lower limit to 2 so we hit it quickly in the test.
	policy := svc.GetRiskPolicy()
	policy.MaxDailyOrders = 2
	svc.SetRiskPolicy(policy)

	// First two submits must reach the broker.
	for i := 0; i < 2; i++ {
		_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
		if err != nil {
			t.Fatalf("submit %d unexpected error: %v", i+1, err)
		}
	}
	if broker.submitCalls != 2 {
		t.Fatalf("expected 2 broker calls before limit, got %d", broker.submitCalls)
	}

	// Third submit must be blocked before the broker.
	_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
	if !errors.Is(err, ErrRiskBlocked) {
		t.Fatalf("expected ErrRiskBlocked at daily limit, got %v", err)
	}
	if broker.submitCalls != 2 {
		t.Errorf("broker must not be called when daily limit is reached, got %d calls", broker.submitCalls)
	}
}

func TestSubmitLiveOrderMaxOpenOrdersBlocksBeforeBroker(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	policy := svc.GetRiskPolicy()
	policy.MaxOpenOrders = 2
	policy.MaxDailyOrders = 0 // disable daily limit to isolate open-order check
	svc.SetRiskPolicy(policy)

	// Submit two open orders.
	for i := 0; i < 2; i++ {
		_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
		if err != nil {
			t.Fatalf("submit %d unexpected error: %v", i+1, err)
		}
	}

	// Third submit must be blocked before the broker.
	_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
	if !errors.Is(err, ErrRiskBlocked) {
		t.Fatalf("expected ErrRiskBlocked at open-order limit, got %v", err)
	}
	if broker.submitCalls != 2 {
		t.Errorf("broker must not be called when open-order limit is reached, got %d calls", broker.submitCalls)
	}
}

func TestSubmitLiveOrderRiskRejectedOrderNotCountedAsOpen(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	svc := NewService(broker)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	policy := svc.GetRiskPolicy()
	policy.MaxOpenOrders = 1
	policy.MaxDailyOrders = 0
	svc.SetRiskPolicy(policy)

	// Submit one open order — at limit now.
	_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
	if err != nil {
		t.Fatalf("first submit unexpected error: %v", err)
	}

	// Second attempt is blocked → creates a rejected order record.
	rejOrder, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
	if !errors.Is(err, ErrRiskBlocked) {
		t.Fatalf("expected ErrRiskBlocked, got %v", err)
	}
	if rejOrder.Status != trading.OrderStatusRejected {
		t.Errorf("expected rejected order, got status %q", rejOrder.Status)
	}

	// After a rejected order, open count must still be 1 (rejected is terminal).
	// Temporarily set limit to 2 to allow a new open order.
	policy.MaxOpenOrders = 2
	svc.SetRiskPolicy(policy)

	_, err = svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-1", "KRX:005930", "market"))
	if err != nil {
		t.Errorf("expected allowed after policy relaxation (rejected order doesn't count as open), got %v", err)
	}
}

func TestGetRiskPolicyReturnsDefault(t *testing.T) {
	svc := NewService(nil)
	p := svc.GetRiskPolicy()
	if p.MaxDailyOrders != 5 {
		t.Errorf("expected default MaxDailyOrders=5, got %d", p.MaxDailyOrders)
	}
	if p.MaxOpenOrders != 3 {
		t.Errorf("expected default MaxOpenOrders=3, got %d", p.MaxOpenOrders)
	}
	if p.AllowShortSelling {
		t.Error("expected default AllowShortSelling=false")
	}
}

// blockingFakeBroker is a BrokerPort whose SubmitOrder blocks until released.
// It signals brokerEntered when the first call arrives so tests can synchronize.
type blockingFakeBroker struct {
	result        OrderResult
	mu            sync.Mutex
	callCount     int
	brokerEntered chan struct{}
	releaseCh     chan struct{}
	once          sync.Once
}

func newBlockingFakeBroker(result OrderResult) *blockingFakeBroker {
	return &blockingFakeBroker{
		result:        result,
		brokerEntered: make(chan struct{}),
		releaseCh:     make(chan struct{}),
	}
}

func (b *blockingFakeBroker) GetCapabilities(_ context.Context, _ string) (trading.BrokerCapability, error) {
	return trading.BrokerCapability{}, nil
}

func (b *blockingFakeBroker) SubmitOrder(_ context.Context, _ string, _ trading.OrderIntent) (OrderResult, error) {
	b.mu.Lock()
	b.callCount++
	b.mu.Unlock()
	b.once.Do(func() { close(b.brokerEntered) })
	<-b.releaseCh
	return b.result, nil
}

func (b *blockingFakeBroker) CancelOrder(_ context.Context, _, _ string) (OrderResult, error) {
	return OrderResult{}, nil
}

func (b *blockingFakeBroker) GetOrderStatus(_ context.Context, _, _ string) (OrderResult, error) {
	return OrderResult{}, nil
}

func (b *blockingFakeBroker) FetchAccountSnapshot(_ context.Context, _ string) (trading.AccountSnapshot, error) {
	return trading.AccountSnapshot{}, nil
}

func (b *blockingFakeBroker) calls() int {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.callCount
}

func TestSubmitLiveOrderConcurrentMaxOpenOrdersBlocksBeforeBroker(t *testing.T) {
	blocking := newBlockingFakeBroker(OrderResult{
		BrokerOrderID: "b-1",
		Status:        trading.OrderStatusSubmitted,
		BrokerStatus:  "ACCEPTED",
	})
	svc := NewService(blocking)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	policy := svc.GetRiskPolicy()
	policy.MaxOpenOrders = 1
	policy.MaxDailyOrders = 0
	svc.SetRiskPolicy(policy)

	req := confirmedReq("acct-1", "KRX:005930", "market")

	var wg sync.WaitGroup
	orders := make([]LiveOrder, 2)
	errs := make([]error, 2)

	wg.Add(1)
	go func() {
		defer wg.Done()
		orders[0], errs[0] = svc.SubmitLiveOrder(context.Background(), req)
	}()

	// Wait until submit 1 is inside the broker (holding the account mutex).
	<-blocking.brokerEntered

	wg.Add(1)
	go func() {
		defer wg.Done()
		orders[1], errs[1] = svc.SubmitLiveOrder(context.Background(), req)
	}()

	// Release the broker so submit 1 stores its order and releases the account mutex.
	close(blocking.releaseCh)
	wg.Wait()

	if blocking.calls() != 1 {
		t.Errorf("expected 1 broker call, got %d", blocking.calls())
	}
	if errs[0] != nil {
		t.Errorf("submit 1: expected success, got %v", errs[0])
	}
	if !errors.Is(errs[1], ErrRiskBlocked) {
		t.Errorf("submit 2: expected ErrRiskBlocked, got %v", errs[1])
	}
	if orders[1].Status != trading.OrderStatusRejected {
		t.Errorf("submit 2: expected rejected order status, got %q", orders[1].Status)
	}
}

func TestSubmitLiveOrderConcurrentMaxDailyOrdersBlocksBeforeBroker(t *testing.T) {
	blocking := newBlockingFakeBroker(OrderResult{
		BrokerOrderID: "b-1",
		Status:        trading.OrderStatusSubmitted,
		BrokerStatus:  "ACCEPTED",
	})
	svc := NewService(blocking)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	policy := svc.GetRiskPolicy()
	policy.MaxDailyOrders = 1
	policy.MaxOpenOrders = 0
	svc.SetRiskPolicy(policy)

	req := confirmedReq("acct-1", "KRX:005930", "market")

	var wg sync.WaitGroup
	orders := make([]LiveOrder, 2)
	errs := make([]error, 2)

	wg.Add(1)
	go func() {
		defer wg.Done()
		orders[0], errs[0] = svc.SubmitLiveOrder(context.Background(), req)
	}()

	<-blocking.brokerEntered

	wg.Add(1)
	go func() {
		defer wg.Done()
		orders[1], errs[1] = svc.SubmitLiveOrder(context.Background(), req)
	}()

	close(blocking.releaseCh)
	wg.Wait()

	if blocking.calls() != 1 {
		t.Errorf("expected 1 broker call, got %d", blocking.calls())
	}
	if errs[0] != nil {
		t.Errorf("submit 1: expected success, got %v", errs[0])
	}
	if !errors.Is(errs[1], ErrRiskBlocked) {
		t.Errorf("submit 2: expected ErrRiskBlocked, got %v", errs[1])
	}
	if orders[1].Status != trading.OrderStatusRejected {
		t.Errorf("submit 2: expected rejected order status, got %q", orders[1].Status)
	}
}

func TestSubmitLiveOrderQueuedKillSwitchBlocksBeforeBroker(t *testing.T) {
	blocking := newBlockingFakeBroker(OrderResult{
		BrokerOrderID: "b-1",
		Status:        trading.OrderStatusSubmitted,
		BrokerStatus:  "ACCEPTED",
	})
	svc := NewService(blocking)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})
	policy := svc.GetRiskPolicy()
	policy.MaxOpenOrders = 0
	policy.MaxDailyOrders = 0
	svc.SetRiskPolicy(policy)

	req := confirmedReq("acct-1", "KRX:005930", "market")

	var wg sync.WaitGroup
	orders := make([]LiveOrder, 2)
	errs := make([]error, 2)

	wg.Add(1)
	go func() {
		defer wg.Done()
		orders[0], errs[0] = svc.SubmitLiveOrder(context.Background(), req)
	}()

	// Wait until submit 1 is inside the broker (holding the account mutex).
	<-blocking.brokerEntered

	wg.Add(1)
	go func() {
		defer wg.Done()
		orders[1], errs[1] = svc.SubmitLiveOrder(context.Background(), req)
	}()

	// Give submit 2 time to reach the account mutex before changing kill switch state.
	time.Sleep(time.Millisecond)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: true, Reason: "operator halt"})
	close(blocking.releaseCh)
	wg.Wait()

	if blocking.calls() != 1 {
		t.Errorf("expected 1 broker call, got %d", blocking.calls())
	}
	if errs[0] != nil {
		t.Errorf("submit 1: expected success, got %v", errs[0])
	}
	if !errors.Is(errs[1], ErrRiskBlocked) {
		t.Errorf("submit 2: expected ErrRiskBlocked, got %v", errs[1])
	}
	if orders[1].Status != trading.OrderStatusRejected {
		t.Errorf("submit 2: expected rejected order, got %q", orders[1].Status)
	}
	if orders[1].RejectionReason != "operator halt" {
		t.Errorf("submit 2: expected rejection reason 'operator halt', got %q", orders[1].RejectionReason)
	}
}

func TestSubmitLiveOrderBrokerUnavailableWithDefaultKillSwitch(t *testing.T) {
	// NewService(nil) default kill switch is halted=true. ErrBrokerUnavailable
	// must take precedence over risk evaluation — no rejected order is created.
	svc := NewService(nil)
	req := confirmedReq("acct-1", "KRX:005930", "market")

	_, err := svc.SubmitLiveOrder(context.Background(), req)
	if !errors.Is(err, ErrBrokerUnavailable) {
		t.Fatalf("expected ErrBrokerUnavailable even with default halted kill switch, got %v", err)
	}
	if errors.Is(err, ErrRiskBlocked) {
		t.Error("ErrBrokerUnavailable must take precedence over ErrRiskBlocked for nil broker")
	}
}

func sampleAccountSnapshot(accountID string) trading.AccountSnapshot {
	return trading.AccountSnapshot{
		Broker:    trading.BrokerKIS,
		AccountID: accountID,
		Cash: []trading.CashBalance{
			{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "5000000"}},
		},
		Positions: []trading.PositionSnapshot{
			{
				InstrumentID: "KRX:005930",
				Side:         trading.OrderSideBuy,
				Quantity:     market.Quantity{Amount: market.Decimal{Value: "10"}},
			},
		},
		SyncedAt: time.Date(2026, 6, 8, 0, 0, 0, 0, time.UTC),
		Stale:    false,
	}
}

func TestSyncLiveAccountSuccess(t *testing.T) {
	snap := sampleAccountSnapshot("op-alias-001")
	broker := &fakeBroker{snapshot: snap}
	svc := NewService(broker)

	got, err := svc.SyncLiveAccount(context.Background(), "op-alias-001")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got.AccountID != "op-alias-001" {
		t.Errorf("account_id mismatch: %q", got.AccountID)
	}
	if got.Broker != trading.BrokerKIS {
		t.Errorf("broker mismatch: %q", got.Broker)
	}
	if len(got.Cash) != 1 {
		t.Fatalf("expected 1 cash entry, got %d", len(got.Cash))
	}
	if got.Stale {
		t.Error("expected Stale=false")
	}
	// Raw account number must not appear in AccountID
	if got.AccountID == "1234567890" {
		t.Error("raw account number must not be exposed in AccountID")
	}
}

func TestSyncLiveAccountNilBrokerReturnsUnavailable(t *testing.T) {
	svc := NewService(nil)
	_, err := svc.SyncLiveAccount(context.Background(), "op-alias-001")
	if !errors.Is(err, ErrBrokerUnavailable) {
		t.Fatalf("expected ErrBrokerUnavailable, got %v", err)
	}
}

func TestSyncLiveAccountBrokerErrorPropagates(t *testing.T) {
	broker := &fakeBroker{snapshotErr: errors.New("connection refused")}
	svc := NewService(broker)
	_, err := svc.SyncLiveAccount(context.Background(), "op-alias-001")
	if err == nil {
		t.Fatal("expected error from broker, got nil")
	}
}

func TestGetLiveAccountSnapshotNotFoundBeforeSync(t *testing.T) {
	svc := NewService(&fakeBroker{})
	_, err := svc.GetLiveAccountSnapshot(context.Background(), "op-alias-001")
	if !errors.Is(err, ErrAccountSnapshotNotFound) {
		t.Fatalf("expected ErrAccountSnapshotNotFound before sync, got %v", err)
	}
}

// fakeAuditStore records all appended audit events and can be configured to
// return an error on the next AppendLiveAuditEvent call.
type fakeAuditStore struct {
	events    []trading.AuditEvent
	appendErr error
	listErr   error
}

func (f *fakeAuditStore) AppendLiveAuditEvent(_ context.Context, ev trading.AuditEvent) error {
	if f.appendErr != nil {
		return f.appendErr
	}
	f.events = append(f.events, ev)
	return nil
}

func (f *fakeAuditStore) ListLiveAuditEvents(_ context.Context, filter trading.AuditFilter) ([]trading.AuditEvent, error) {
	if f.listErr != nil {
		return nil, f.listErr
	}
	var out []trading.AuditEvent
	for _, ev := range f.events {
		if filter.AccountID != "" && ev.AccountID != filter.AccountID {
			continue
		}
		if filter.OrderID != "" && ev.OrderID != filter.OrderID {
			continue
		}
		if filter.EventType != "" && ev.Type != filter.EventType {
			continue
		}
		out = append(out, ev)
		if filter.Limit > 0 && len(out) >= filter.Limit {
			break
		}
	}
	return out, nil
}

func auditStoreEvents(store *fakeAuditStore) []trading.AuditEventType {
	types := make([]trading.AuditEventType, len(store.events))
	for i, ev := range store.events {
		types[i] = ev.Type
	}
	return types
}

func TestSubmitLiveOrderEmitsSubmitConfirmedAudit(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	audit := &fakeAuditStore{}
	svc := NewService(broker)
	svc.SetAuditStore(audit)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	req := confirmedReq("acct-audit", "KRX:005930", "market")
	req.Confirmation.OperatorID = "op-1"
	order, err := svc.SubmitLiveOrder(context.Background(), req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// A successful submit emits submit_confirmed (operator gate) then broker_accepted.
	evsByType := make(map[trading.AuditEventType][]trading.AuditEvent)
	for _, ev := range audit.events {
		evsByType[ev.Type] = append(evsByType[ev.Type], ev)
	}

	submitEvents := evsByType[trading.AuditEventTypeSubmitConfirmed]
	if len(submitEvents) != 1 {
		t.Fatalf("expected 1 submit_confirmed event, got %d (all types: %v)", len(submitEvents), auditStoreEvents(audit))
	}
	sc := submitEvents[0]
	if sc.AccountID != "acct-audit" {
		t.Errorf("submit_confirmed account_id mismatch: %q", sc.AccountID)
	}
	if sc.Actor != "op-1" {
		t.Errorf("submit_confirmed actor mismatch: %q", sc.Actor)
	}
	if sc.EventID == "" {
		t.Error("submit_confirmed event_id must be non-empty")
	}
	// submit_confirmed is emitted before broker call, so OrderID is not yet set.
	if sc.OrderID != "" {
		t.Errorf("submit_confirmed should not carry order_id (emitted before broker), got %q", sc.OrderID)
	}

	acceptedEvents := evsByType[trading.AuditEventTypeBrokerAccepted]
	if len(acceptedEvents) != 1 {
		t.Fatalf("expected 1 broker_accepted event, got %d", len(acceptedEvents))
	}
	ba := acceptedEvents[0]
	if ba.OrderID != order.ID {
		t.Errorf("broker_accepted order_id mismatch: got %q, want %q", ba.OrderID, order.ID)
	}
	if ba.AccountID != "acct-audit" {
		t.Errorf("broker_accepted account_id mismatch: %q", ba.AccountID)
	}
}

func TestSubmitLiveOrderBrokerRejectedEmitsBothAuditEvents(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "", Status: trading.OrderStatusRejected, BrokerStatus: "REJECTED", RejectionReason: "insufficient balance"},
	}
	audit := &fakeAuditStore{}
	svc := NewService(broker)
	svc.SetAuditStore(audit)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	req := confirmedReq("acct-audit", "KRX:005930", "market")
	req.Confirmation.OperatorID = "op-1"
	order, err := svc.SubmitLiveOrder(context.Background(), req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if order.Status != trading.OrderStatusRejected {
		t.Fatalf("expected rejected order, got %q", order.Status)
	}

	evsByType := make(map[trading.AuditEventType][]trading.AuditEvent)
	for _, ev := range audit.events {
		evsByType[ev.Type] = append(evsByType[ev.Type], ev)
	}

	if len(evsByType[trading.AuditEventTypeSubmitConfirmed]) != 1 {
		t.Fatalf("expected 1 submit_confirmed, got %d", len(evsByType[trading.AuditEventTypeSubmitConfirmed]))
	}
	rejectedEvents := evsByType[trading.AuditEventTypeBrokerRejected]
	if len(rejectedEvents) != 1 {
		t.Fatalf("expected 1 broker_rejected event, got %d", len(rejectedEvents))
	}
	br := rejectedEvents[0]
	if br.OrderID != order.ID {
		t.Errorf("broker_rejected order_id mismatch: got %q, want %q", br.OrderID, order.ID)
	}
	if br.Reason != "insufficient balance" {
		t.Errorf("broker_rejected reason mismatch: %q", br.Reason)
	}
}

func TestSubmitLiveOrderRiskBlockedEmitsRiskBlockedAudit(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	audit := &fakeAuditStore{}
	svc := NewService(broker)
	svc.SetAuditStore(audit)
	// Kill switch halted by default → ErrRiskBlocked

	_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-audit", "KRX:005930", "market"))
	if !errors.Is(err, ErrRiskBlocked) {
		t.Fatalf("expected ErrRiskBlocked, got %v", err)
	}

	var riskEvents []trading.AuditEvent
	for _, ev := range audit.events {
		if ev.Type == trading.AuditEventTypeRiskBlocked {
			riskEvents = append(riskEvents, ev)
		}
	}
	if len(riskEvents) != 1 {
		t.Fatalf("expected 1 risk_blocked event, got %d", len(riskEvents))
	}
	if riskEvents[0].AccountID != "acct-audit" {
		t.Errorf("account_id mismatch: %q", riskEvents[0].AccountID)
	}
}

func TestSubmitLiveOrderAuditFailureReturnsErrAuditFailed(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
	}
	audit := &fakeAuditStore{appendErr: errors.New("db down")}
	svc := NewService(broker)
	svc.SetAuditStore(audit)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	_, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-audit", "KRX:005930", "market"))
	if !errors.Is(err, ErrAuditFailed) {
		t.Fatalf("expected ErrAuditFailed when audit store errors, got %v", err)
	}
}

func TestCancelLiveOrderEmitsCancelConfirmedAudit(t *testing.T) {
	broker := &fakeBroker{
		submitResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusSubmitted, BrokerStatus: "ACCEPTED"},
		cancelResult: OrderResult{BrokerOrderID: "b-1", Status: trading.OrderStatusCanceled, BrokerStatus: "CANCELED"},
	}
	audit := &fakeAuditStore{}
	svc := NewService(broker)
	svc.SetAuditStore(audit)
	svc.SetKillSwitch(trading.KillSwitchState{Halted: false})

	order, err := svc.SubmitLiveOrder(context.Background(), confirmedReq("acct-audit", "KRX:005930", "limit"))
	if err != nil {
		t.Fatalf("submit error: %v", err)
	}
	_, err = svc.CancelLiveOrder(context.Background(), "acct-audit", order.ID)
	if err != nil {
		t.Fatalf("cancel error: %v", err)
	}

	var cancelEvents []trading.AuditEvent
	for _, ev := range audit.events {
		if ev.Type == trading.AuditEventTypeCancelConfirmed {
			cancelEvents = append(cancelEvents, ev)
		}
	}
	if len(cancelEvents) != 1 {
		t.Fatalf("expected 1 cancel_confirmed event, got %d", len(cancelEvents))
	}
	if cancelEvents[0].OrderID != order.ID {
		t.Errorf("order_id mismatch: got %q, want %q", cancelEvents[0].OrderID, order.ID)
	}
}

func TestSyncLiveAccountEmitsAccountSyncSuccessAudit(t *testing.T) {
	snap := sampleAccountSnapshot("op-alias-001")
	broker := &fakeBroker{snapshot: snap}
	audit := &fakeAuditStore{}
	svc := NewService(broker)
	svc.SetAuditStore(audit)

	_, err := svc.SyncLiveAccount(context.Background(), "op-alias-001")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var syncEvents []trading.AuditEvent
	for _, ev := range audit.events {
		if ev.Type == trading.AuditEventTypeAccountSyncSuccess {
			syncEvents = append(syncEvents, ev)
		}
	}
	if len(syncEvents) != 1 {
		t.Fatalf("expected 1 account_sync_success event, got %d", len(syncEvents))
	}
	if syncEvents[0].AccountID != "op-alias-001" {
		t.Errorf("account_id mismatch: %q", syncEvents[0].AccountID)
	}
	if syncEvents[0].Broker != trading.BrokerKIS {
		t.Errorf("broker mismatch: %q", syncEvents[0].Broker)
	}
}

func TestSyncLiveAccountBrokerErrorEmitsAccountSyncFailureAudit(t *testing.T) {
	broker := &fakeBroker{snapshotErr: errors.New("network timeout")}
	audit := &fakeAuditStore{}
	svc := NewService(broker)
	svc.SetAuditStore(audit)

	_, err := svc.SyncLiveAccount(context.Background(), "op-alias-001")
	if err == nil {
		t.Fatal("expected error from broker")
	}

	var failEvents []trading.AuditEvent
	for _, ev := range audit.events {
		if ev.Type == trading.AuditEventTypeAccountSyncFailure {
			failEvents = append(failEvents, ev)
		}
	}
	if len(failEvents) != 1 {
		t.Fatalf("expected 1 account_sync_failure event, got %d", len(failEvents))
	}
}

func TestAppendAuditRejectsForbiddenPayloadKey(t *testing.T) {
	audit := &fakeAuditStore{}
	svc := NewService(nil)
	svc.SetAuditStore(audit)

	ev := trading.AuditEvent{
		EventID:   "aud-bad",
		Type:      trading.AuditEventTypeSubmitConfirmed,
		AccountID: "acct-1",
		Payload:   map[string]string{"token": "raw-secret"},
	}
	err := svc.appendAudit(context.Background(), ev)
	if !errors.Is(err, ErrAuditFailed) {
		t.Fatalf("expected ErrAuditFailed for forbidden payload key, got %v", err)
	}
	if len(audit.events) != 0 {
		t.Error("store must not be called when payload contains forbidden key")
	}
}

func TestListLiveAuditEventsNoStore(t *testing.T) {
	svc := NewService(nil)
	events, err := svc.ListLiveAuditEvents(context.Background(), trading.AuditFilter{AccountID: "acct-1"})
	if err != nil {
		t.Fatalf("expected nil error when no store, got %v", err)
	}
	if events != nil {
		t.Errorf("expected nil events when no store, got %v", events)
	}
}

func TestListLiveAuditEventsDelegatesFilter(t *testing.T) {
	audit := &fakeAuditStore{}
	svc := NewService(nil)
	svc.SetAuditStore(audit)

	for i := 0; i < 3; i++ {
		_ = audit.AppendLiveAuditEvent(context.Background(), trading.AuditEvent{
			EventID:   fmt.Sprintf("aud-%d", i),
			Type:      trading.AuditEventTypeSubmitConfirmed,
			AccountID: "acct-1",
			OrderID:   fmt.Sprintf("lo-acct-1-%d", i),
		})
	}

	events, err := svc.ListLiveAuditEvents(context.Background(), trading.AuditFilter{AccountID: "acct-1", Limit: 2})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(events) != 2 {
		t.Errorf("expected 2 events with limit=2, got %d", len(events))
	}
}

func TestGetLiveAccountSnapshotAfterSync(t *testing.T) {
	snap := sampleAccountSnapshot("op-alias-001")
	broker := &fakeBroker{snapshot: snap}
	svc := NewService(broker)

	if _, err := svc.SyncLiveAccount(context.Background(), "op-alias-001"); err != nil {
		t.Fatalf("sync failed: %v", err)
	}

	got, err := svc.GetLiveAccountSnapshot(context.Background(), "op-alias-001")
	if err != nil {
		t.Fatalf("get failed: %v", err)
	}
	if got.AccountID != "op-alias-001" {
		t.Errorf("account_id mismatch: %q", got.AccountID)
	}
	if len(got.Positions) != 1 {
		t.Errorf("expected 1 position, got %d", len(got.Positions))
	}
}
