package market

import "testing"

func TestUniverseSelectorVocabulary(t *testing.T) {
	cases := []struct {
		kind UniverseSelectorKind
		want string
	}{
		{UniverseSelectorWatchlist, "watchlist"},
		{UniverseSelectorExchange, "exchange"},
		{UniverseSelectorSector, "sector"},
	}
	for _, c := range cases {
		if string(c.kind) != c.want {
			t.Errorf("selector kind %q: got %q, want %q", c.want, string(c.kind), c.want)
		}
	}

	providers := []struct {
		provider Provider
		want     string
	}{
		{ProviderKIS, "kis"},
		{ProviderKRX, "krx"},
		{ProviderDART, "dart"},
		{ProviderYahoo, "yahoo"},
	}
	for _, c := range providers {
		if string(c.provider) != c.want {
			t.Errorf("provider %q: got %q, want %q", c.want, string(c.provider), c.want)
		}
	}
}

func TestUniverseSelectorFields(t *testing.T) {
	sel := UniverseSelector{
		Kind:    UniverseSelectorExchange,
		Market:  MarketKR,
		Venue:   VenueKRX,
		Symbols: []string{"005930", "000660"},
		Name:    "semiconductors",
	}

	if sel.Kind != UniverseSelectorExchange {
		t.Errorf("Kind mismatch: got %v", sel.Kind)
	}
	if sel.Market != MarketKR || sel.Venue != VenueKRX {
		t.Errorf("market/venue mismatch: got %v/%v", sel.Market, sel.Venue)
	}
	if len(sel.Symbols) != 2 {
		t.Errorf("Symbols length: got %d, want 2", len(sel.Symbols))
	}
}

func TestMarketVenueMetadata(t *testing.T) {
	cases := []struct {
		venue        Venue
		wantOk       bool
		wantMarket   Market
		wantCurrency Currency
		wantTimezone string
	}{
		{VenueKRX, true, MarketKR, CurrencyKRW, "Asia/Seoul"},
		{VenueNASDAQ, true, MarketUS, CurrencyUSD, "America/New_York"},
		{VenueNYSE, true, MarketUS, CurrencyUSD, "America/New_York"},
		{Venue("UNKNOWN"), false, Market(""), Currency(""), ""},
	}
	for _, c := range cases {
		meta, ok := GetVenueMetadata(c.venue)
		if ok != c.wantOk {
			t.Errorf("GetVenueMetadata(%q) ok: got %v, want %v", c.venue, ok, c.wantOk)
		}
		if ok {
			if meta.Market != c.wantMarket || meta.Currency != c.wantCurrency || meta.Timezone != c.wantTimezone {
				t.Errorf("GetVenueMetadata(%q) meta mismatch: got %+v, want Market: %v, Currency: %v, Timezone: %v", c.venue, meta, c.wantMarket, c.wantCurrency, c.wantTimezone)
			}
		}
	}
}

func TestTimeframeVocabulary(t *testing.T) {
	cases := []struct {
		timeframe Timeframe
		want      string
	}{
		{TimeframeMonthly, "1mo"},
		{TimeframeDaily, "1d"},
		{TimeframeMin1, "1m"},
		{TimeframeMin5, "5m"},
	}
	for _, c := range cases {
		if string(c.timeframe) != c.want {
			t.Errorf("timeframe %q: got %q, want %q", c.want, string(c.timeframe), c.want)
		}
	}
}

func TestProviderCapabilityRequireDailyBars(t *testing.T) {
	// A KIS-like capability serving both KR and US daily bars.
	kis := ProviderCapability{
		Provider: ProviderKIS,
		Venues: map[Venue]bool{
			VenueKRX:    true,
			VenueNASDAQ: true,
			VenueNYSE:   true,
		},
		Timeframes: map[Timeframe]bool{TimeframeDaily: true},
	}
	// A narrower KR-only provider that must reject US venues.
	krOnly := ProviderCapability{
		Provider:   ProviderKRX,
		Venues:     map[Venue]bool{VenueKRX: true},
		Timeframes: map[Timeframe]bool{TimeframeDaily: true},
	}
	// A non-price-data provider can exist in the same provider vocabulary
	// without accidentally satisfying daily bar import capability.
	metadataOnly := ProviderCapability{
		Provider:   ProviderDART,
		Venues:     map[Venue]bool{VenueKRX: true},
		Timeframes: map[Timeframe]bool{},
	}

	sel := func(market Market, venue Venue) UniverseSelector {
		return UniverseSelector{Kind: UniverseSelectorWatchlist, Market: market, Venue: venue, Symbols: []string{"X"}}
	}

	cases := []struct {
		name     string
		cap      ProviderCapability
		provider Provider
		selector UniverseSelector
		wantErr  bool
	}{
		{"kis allows KR/KRX", kis, ProviderKIS, sel(MarketKR, VenueKRX), false},
		{"kis allows US/NASDAQ", kis, ProviderKIS, sel(MarketUS, VenueNASDAQ), false},
		{"kis allows US/NYSE", kis, ProviderKIS, sel(MarketUS, VenueNYSE), false},
		{"provider mismatch rejected", kis, Provider("yahoo"), sel(MarketUS, VenueNASDAQ), true},
		{"missing venue rejected", kis, ProviderKIS, sel(MarketUS, ""), true},
		{"unknown venue rejected", kis, ProviderKIS, sel(MarketUS, Venue("LSE")), true},
		{"market/venue mismatch rejected", kis, ProviderKIS, sel(MarketKR, VenueNASDAQ), true},
		{"kr-only capability rejects US", krOnly, ProviderKRX, sel(MarketUS, VenueNASDAQ), true},
		{"kr-only capability allows KR", krOnly, ProviderKRX, sel(MarketKR, VenueKRX), false},
		{"metadata provider rejects daily bars", metadataOnly, ProviderDART, sel(MarketKR, VenueKRX), true},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			err := c.cap.RequireDailyBars(c.provider, c.selector)
			if c.wantErr && err == nil {
				t.Errorf("expected error, got nil")
			}
			if !c.wantErr && err != nil {
				t.Errorf("expected no error, got %v", err)
			}
		})
	}
}

func TestAssetTypeVocabulary(t *testing.T) {
	if string(AssetTypeEquity) != "equity" {
		t.Errorf("AssetTypeEquity: got %q, want %q", string(AssetTypeEquity), "equity")
	}
	if string(AssetTypeETF) != "etf" {
		t.Errorf("AssetTypeETF: got %q, want %q", string(AssetTypeETF), "etf")
	}
}

func TestProviderCapabilityMatrix(t *testing.T) {
	// KIS capability with explicit rules for KR/US equity/ETF daily.
	kis := ProviderCapability{
		Provider: ProviderKIS,
		Venues: map[Venue]bool{
			VenueKRX:    true,
			VenueNASDAQ: true,
			VenueNYSE:   true,
		},
		Timeframes: map[Timeframe]bool{
			TimeframeDaily: true,
		},
		Rules: []ProviderCapabilityRule{
			{
				Market: MarketKR,
				Venue:  VenueKRX,
				AssetTypes: map[AssetType]bool{
					AssetTypeEquity: true,
					AssetTypeETF:    true,
				},
				Timeframes: map[Timeframe]bool{
					TimeframeDaily: true,
				},
			},
			{
				Market: MarketUS,
				Venue:  VenueNASDAQ,
				AssetTypes: map[AssetType]bool{
					AssetTypeEquity: true,
					AssetTypeETF:    true,
				},
				Timeframes: map[Timeframe]bool{
					TimeframeDaily: true,
				},
			},
		},
	}

	// rulesOnlyKIS is a capability with NO Venues map - only Rules.
	// This is the core regression test for REVIEW_API-1.
	rulesOnlyKIS := ProviderCapability{
		Provider: ProviderKIS,
		Venues:   map[Venue]bool{},
		Rules: []ProviderCapabilityRule{
			{
				Market: MarketKR,
				Venue:  VenueKRX,
				AssetTypes: map[AssetType]bool{
					AssetTypeEquity: true,
					AssetTypeETF:    true,
				},
				Timeframes: map[Timeframe]bool{
					TimeframeDaily:   true,
					TimeframeMonthly: false,
					TimeframeMin1:    false,
					TimeframeMin5:    false,
				},
			},
			{
				Market: MarketUS,
				Venue:  VenueNASDAQ,
				AssetTypes: map[AssetType]bool{
					AssetTypeEquity: true,
					AssetTypeETF:    true,
				},
				Timeframes: map[Timeframe]bool{
					TimeframeDaily:   true,
					TimeframeMonthly: false,
					TimeframeMin1:    false,
					TimeframeMin5:    false,
				},
			},
		},
	}

	cases := []struct {
		name         string
		req          ProviderCapabilityRequest
		wantAccepted bool
		wantReason   string
	}{
		// Accepted cases
		{"KIS KR KRX equity daily", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		{"KIS KR KRX etf daily", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeETF, Timeframe: TimeframeDaily,
		}, true, ""},
		{"KIS US NASDAQ equity daily", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		{"KIS US NASDAQ etf daily", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeETF, Timeframe: TimeframeDaily,
		}, true, ""},
		// Rejected: monthly
		{"KIS KR KRX equity monthly rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMonthly,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"1mo\" on venue \"KRX\""},
		// Rejected: minute_1
		{"KIS KR KRX equity minute_1 rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMin1,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"1m\" on venue \"KRX\""},
		// Rejected: minute_5
		{"KIS KR KRX equity minute_5 rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMin5,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"5m\" on venue \"KRX\""},
		// Rejected: provider mismatch
		{"provider mismatch rejected", ProviderCapabilityRequest{
			Provider: ProviderYahoo, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, false, "provider \"yahoo\" does not match capability provider \"kis\""},
		// Rejected: unknown venue
		{"unknown venue rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: Venue("LSE"),
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, false, "provider \"kis\" does not support venue \"LSE\""},
		// Rejected: market/venue mismatch
		{"market/venue mismatch rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, false, "market \"KR\" does not match venue \"NASDAQ\" market \"US\""},
		// Rejected: no matching rule (asset type not in rule)
		{"no matching rule", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNYSE,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, false, "no capability rule matches market \"US\" venue \"NYSE\" asset type \"equity\" timeframe \"1d\""},
		// REVIEW_API-1: empty market should use venue metadata as effective market
		{"KIS KRX empty market accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		{"KIS NASDAQ empty market accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		// empty market must still reject cross-market (e.g. US venue with explicit KR)
		{"KIS NASDAQ explicit KR rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, false, "market \"KR\" does not match venue \"NASDAQ\" market \"US\""},
	}

	// rules-only regression test: REVIEW_API-1 - Venues must not be required.
	rulesOnlyCases := []struct {
		name         string
		req          ProviderCapabilityRequest
		wantAccepted bool
		wantReason   string
	}{
		// KR accepted
		{"rules-only KR KRX equity daily accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		{"rules-only KR KRX etf daily accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeETF, Timeframe: TimeframeDaily,
		}, true, ""},
		// KR rejected
		{"rules-only KR KRX equity monthly rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMonthly,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"1mo\" on venue \"KRX\""},
		{"rules-only KR KRX equity minute_1 rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMin1,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"1m\" on venue \"KRX\""},
		{"rules-only KR KRX equity minute_5 rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketKR, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMin5,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"5m\" on venue \"KRX\""},
		// US/NASDAQ accepted.
		{"rules-only US NASDAQ equity daily accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		{"rules-only US NASDAQ etf daily accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeETF, Timeframe: TimeframeDaily,
		}, true, ""},
		// US/NASDAQ rejected
		{"rules-only US NASDAQ equity monthly rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMonthly,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"1mo\" on venue \"NASDAQ\""},
		{"rules-only US NASDAQ equity minute_1 rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMin1,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"1m\" on venue \"NASDAQ\""},
		{"rules-only US NASDAQ equity minute_5 rejected", ProviderCapabilityRequest{
			Provider: ProviderKIS, Market: MarketUS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeMin5,
		}, false, "provider \"kis\" does not support asset type \"equity\" timeframe \"5m\" on venue \"NASDAQ\""},
		// REVIEW_API-1: empty market fallback to venue metadata
		{"rules-only KRX empty market accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Venue: VenueKRX,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
		{"rules-only NASDAQ empty market accepted", ProviderCapabilityRequest{
			Provider: ProviderKIS, Venue: VenueNASDAQ,
			AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
		}, true, ""},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			dec := kis.CheckBars(c.req)
			if dec.Accepted != c.wantAccepted {
				t.Errorf("CheckBars(%q) accepted: got %v, want %v", c.name, dec.Accepted, c.wantAccepted)
			}
			if c.wantReason != "" && dec.Reason != c.wantReason {
				t.Errorf("CheckBars(%q) reason: got %q, want %q", c.name, dec.Reason, c.wantReason)
			}
		})
	}

	// rules-only regression test
	for _, c := range rulesOnlyCases {
		t.Run("rules-only/"+c.name, func(t *testing.T) {
			dec := rulesOnlyKIS.CheckBars(c.req)
			if dec.Accepted != c.wantAccepted {
				t.Errorf("CheckBars(%q) accepted: got %v, want %v", c.name, dec.Accepted, c.wantAccepted)
			}
			if c.wantReason != "" && dec.Reason != c.wantReason {
				t.Errorf("CheckBars(%q) reason: got %q, want %q", c.name, dec.Reason, c.wantReason)
			}
		})
	}
}

// TestCheckBarsEmptyMarketEmptyVenueReject ensures CheckBars rejects when both
// market and venue are empty so that empty-market fallback cannot accidentally
// match an unrelated venue.
func TestCheckBarsEmptyMarketEmptyVenueReject(t *testing.T) {
	kis := ProviderCapability{
		Provider: ProviderKIS,
		Rules: []ProviderCapabilityRule{
			{
				Market: MarketKR,
				Venue:  VenueKRX,
				AssetTypes: map[AssetType]bool{
					AssetTypeEquity: true,
				},
				Timeframes: map[Timeframe]bool{
					TimeframeDaily: true,
				},
			},
		},
	}
	dec := kis.CheckBars(ProviderCapabilityRequest{
		Provider: ProviderKIS,
		// Market and Venue both empty.
		AssetType: AssetTypeEquity, Timeframe: TimeframeDaily,
	})
	if dec.Accepted {
		t.Error("expected rejection for empty venue, got accepted")
	}
}
