package market

import (
	"fmt"
	"time"
)

type Market string

const (
	MarketKR Market = "KR"
	MarketUS Market = "US"
)

type Venue string

const (
	VenueKRX    Venue = "KRX"
	VenueNASDAQ Venue = "NASDAQ"
	VenueNYSE   Venue = "NYSE"
)

type Currency string

const (
	CurrencyKRW Currency = "KRW"
	CurrencyUSD Currency = "USD"
)

type Timeframe string

const (
	TimeframeMonthly Timeframe = "1mo"
	TimeframeDaily   Timeframe = "1d"
	TimeframeMin1    Timeframe = "1m"
	TimeframeMin5    Timeframe = "5m"
)

type InstrumentID string

type AssetType string

const (
	AssetTypeEquity AssetType = "equity"
	AssetTypeETF    AssetType = "etf"
)

type VenueMetadata struct {
	Venue    Venue
	Market   Market
	Currency Currency
	Timezone string
}

func GetVenueMetadata(v Venue) (VenueMetadata, bool) {
	switch v {
	case VenueKRX:
		return VenueMetadata{
			Venue:    VenueKRX,
			Market:   MarketKR,
			Currency: CurrencyKRW,
			Timezone: "Asia/Seoul",
		}, true
	case VenueNASDAQ:
		return VenueMetadata{
			Venue:    VenueNASDAQ,
			Market:   MarketUS,
			Currency: CurrencyUSD,
			Timezone: "America/New_York",
		}, true
	case VenueNYSE:
		return VenueMetadata{
			Venue:    VenueNYSE,
			Market:   MarketUS,
			Currency: CurrencyUSD,
			Timezone: "America/New_York",
		}, true
	default:
		return VenueMetadata{}, false
	}
}

type Instrument struct {
	ID              InstrumentID
	Market          Market
	Venue           Venue
	Symbol          string
	Name            string
	Currency        Currency
	AssetType       AssetType
	ProviderSymbols map[string]string
}

type Provider string

const (
	ProviderKIS   Provider = "kis"
	ProviderKRX   Provider = "krx"
	ProviderDART  Provider = "dart"
	ProviderYahoo Provider = "yahoo"
)

// ProviderCapability declares the venues and timeframes a market data provider
// can serve. It keeps provider distinct from market: one provider (KIS) can
// declare several venues across multiple markets, while a narrower future
// provider can declare KR-only coverage without changing the import boundary.
// Rules holds the provider/market/venue/asset type/timeframe capability matrix.
type ProviderCapability struct {
	Provider   Provider
	Venues     map[Venue]bool
	Timeframes map[Timeframe]bool
	Rules      []ProviderCapabilityRule
}

// SupportsDailyBars reports whether the capability covers daily bars for venue.
func (c ProviderCapability) SupportsDailyBars(venue Venue) bool {
	return c.Venues[venue] && c.Timeframes[TimeframeDaily]
}

// ProviderCapabilityRule defines a single row in the capability matrix.
// Each rule matches a market/venue combination, a set of allowed asset types
// and a set of allowed timeframes.
type ProviderCapabilityRule struct {
	Market     Market
	Venue      Venue
	AssetTypes map[AssetType]bool
	Timeframes map[Timeframe]bool
}

// ProviderCapabilityRequest is a call-site request that describes what
// provider/market/venue/asset type/timeframe combination the caller needs.
type ProviderCapabilityRequest struct {
	Provider  Provider
	Market    Market
	Venue     Venue
	AssetType AssetType
	Timeframe Timeframe
}

// ProviderCapabilityDecision holds the result of a capability check.
type ProviderCapabilityDecision struct {
	Accepted bool
	Reason   string
}

// CheckBars evaluates the full provider/market/venue/asset type/timeframe
// capability matrix and returns whether the request is accepted or rejected
// with a stable reason string.
func (c ProviderCapability) CheckBars(req ProviderCapabilityRequest) ProviderCapabilityDecision {
	if req.Provider != c.Provider {
		return ProviderCapabilityDecision{
			Accepted: false,
			Reason:   fmt.Sprintf("provider %q does not match capability provider %q", req.Provider, c.Provider),
		}
	}
	// Legacy Venues fallback: if Rules is empty, check Venues for backward
	// compatibility.  When Rules is populated, venue presence is validated by
	// the rule matcher below, so the legacy Venues map is no longer required.
	if len(c.Rules) == 0 && !c.Venues[req.Venue] {
		return ProviderCapabilityDecision{
			Accepted: false,
			Reason:   fmt.Sprintf("provider %q does not support venue %q", req.Provider, req.Venue),
		}
	}
	meta, ok := GetVenueMetadata(req.Venue)
	if !ok {
		return ProviderCapabilityDecision{
			Accepted: false,
			Reason:   fmt.Sprintf("provider %q does not support venue %q", req.Provider, req.Venue),
		}
	}
	if req.Market != "" && req.Market != meta.Market {
		return ProviderCapabilityDecision{
			Accepted: false,
			Reason:   fmt.Sprintf("market %q does not match venue %q market %q", req.Market, req.Venue, meta.Market),
		}
	}
	if len(c.Rules) == 0 {
		return ProviderCapabilityDecision{
			Accepted: false,
			Reason:   fmt.Sprintf("no capability rules defined for provider %q", c.Provider),
		}
	}
	// When the request market is empty, derive the effective market from the
	// venue metadata so that callers without market context (e.g. the daily
	// import job handler) can still match capability rules by venue alone.
	effectiveMarket := req.Market
	if effectiveMarket == "" {
		effectiveMarket = meta.Market
	}
	var matched bool
	for _, rule := range c.Rules {
		if rule.Market != effectiveMarket || rule.Venue != req.Venue {
			continue
		}
		// Empty AssetType is treated as a wildcard so that callers without
		// asset type context (e.g. the daily import job handler) can match
		// any rule for the given market/venue/timeframe.
		if req.AssetType != "" && !rule.AssetTypes[req.AssetType] {
			continue
		}
		if !rule.Timeframes[req.Timeframe] {
			return ProviderCapabilityDecision{
				Accepted: false,
				Reason: fmt.Sprintf("provider %q does not support asset type %q timeframe %q on venue %q",
					req.Provider, req.AssetType, req.Timeframe, req.Venue),
			}
		}
		matched = true
		break
	}
	if !matched {
		return ProviderCapabilityDecision{
			Accepted: false,
			Reason:   fmt.Sprintf("no capability rule matches market %q venue %q asset type %q timeframe %q", effectiveMarket, req.Venue, req.AssetType, req.Timeframe),
		}
	}
	return ProviderCapabilityDecision{
		Accepted: true,
		Reason:   "",
	}
}

// RequireDailyBars is the pre-import gate that rejects unsupported
// provider/market/venue combinations before any fetch. It validates that the
// request provider matches this capability, that the selector pins a venue this
// capability supports, that the selector market (when set) agrees with the
// venue's market, and that the capability covers daily bars for that venue.
// Deprecated: use CheckBars instead. This wrapper is kept for backward
// compatibility with existing call sites until full rollout.
func (c ProviderCapability) RequireDailyBars(provider Provider, selector UniverseSelector) error {
	if provider != c.Provider {
		return fmt.Errorf("provider %q does not match capability provider %q", provider, c.Provider)
	}
	if selector.Venue == "" {
		return fmt.Errorf("provider %q daily bar import requires a venue", provider)
	}
	meta, ok := GetVenueMetadata(selector.Venue)
	if !ok {
		return fmt.Errorf("provider %q does not support venue %q", provider, selector.Venue)
	}
	if selector.Market != "" && selector.Market != meta.Market {
		return fmt.Errorf("selector market %q does not match venue %q market %q", selector.Market, selector.Venue, meta.Market)
	}
	if !c.SupportsDailyBars(selector.Venue) {
		return fmt.Errorf("provider %q does not support daily bars for venue %q", provider, selector.Venue)
	}
	return nil
}

type UniverseSelectorKind string

const (
	UniverseSelectorWatchlist UniverseSelectorKind = "watchlist"
	UniverseSelectorExchange  UniverseSelectorKind = "exchange"
	UniverseSelectorSector    UniverseSelectorKind = "sector"
)

type UniverseSelector struct {
	Kind    UniverseSelectorKind
	Market  Market
	Venue   Venue
	Symbols []string
	Name    string
}

type Decimal struct {
	Value string
}

type Price struct {
	Currency Currency
	Amount   Decimal
}

type Quantity struct {
	Amount Decimal
}

type Bar struct {
	InstrumentID InstrumentID
	Timeframe    Timeframe
	Timestamp    time.Time
	Open         Price
	High         Price
	Low          Price
	Close        Price
	Volume       Quantity
}
