package tushare

import (
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
)

// Field lists and camelCase projection maps mirroring the upstream executor
// tables. The Tushare API speaks snake_case field names; the projected output
// keeps the upstream camelCase names exactly.

var stockBasicFields = []string{
	"ts_code", "symbol", "name", "area", "industry", "market", "exchange",
	"list_status", "list_date", "delist_date", "is_hs",
}

var tradeCalendarFields = []string{"exchange", "cal_date", "is_open", "pretrade_date"}

var dailyQuoteFields = []string{
	"ts_code", "trade_date", "open", "high", "low", "close", "pre_close",
	"change", "pct_chg", "vol", "amount",
}

var dailyBasicFields = []string{
	"ts_code", "trade_date", "close", "turnover_rate", "turnover_rate_f",
	"volume_ratio", "pe", "pe_ttm", "pb", "ps", "ps_ttm", "dv_ratio", "dv_ttm",
	"total_share", "float_share", "free_share", "total_mv", "circ_mv",
}

var adjustmentFactorFields = []string{"ts_code", "trade_date", "adj_factor"}

var shareholderTradeFields = []string{
	"ts_code", "ann_date", "holder_name", "holder_type", "in_de", "change_vol",
	"change_ratio", "after_share", "after_ratio", "avg_price", "total_share",
	"begin_date", "close_date",
}

var stockBasicFieldMap = map[string]string{
	"ts_code": "tsCode", "symbol": "symbol", "name": "name", "area": "area",
	"industry": "industry", "market": "market", "exchange": "exchange",
	"list_status": "listStatus", "list_date": "listDate",
	"delist_date": "delistDate", "is_hs": "isHs",
}

var tradeCalendarFieldMap = map[string]string{
	"exchange": "exchange", "cal_date": "calDate", "is_open": "isOpen",
	"pretrade_date": "pretradeDate",
}

var dailyQuoteFieldMap = map[string]string{
	"ts_code": "tsCode", "trade_date": "tradeDate", "open": "open",
	"high": "high", "low": "low", "close": "close", "pre_close": "preClose",
	"change": "change", "pct_chg": "pctChg", "vol": "vol", "amount": "amount",
}

var dailyBasicFieldMap = map[string]string{
	"ts_code": "tsCode", "trade_date": "tradeDate", "close": "close",
	"turnover_rate": "turnoverRate", "turnover_rate_f": "turnoverRateF",
	"volume_ratio": "volumeRatio", "pe": "pe", "pe_ttm": "peTtm", "pb": "pb",
	"ps": "ps", "ps_ttm": "psTtm", "dv_ratio": "dvRatio", "dv_ttm": "dvTtm",
	"total_share": "totalShare", "float_share": "floatShare",
	"free_share": "freeShare", "total_mv": "totalMv", "circ_mv": "circMv",
}

var adjustmentFactorFieldMap = map[string]string{
	"ts_code": "tsCode", "trade_date": "tradeDate", "adj_factor": "adjFactor",
}

var shareholderTradeFieldMap = map[string]string{
	"ts_code": "tsCode", "ann_date": "announcementDate",
	"holder_name": "holderName", "holder_type": "holderType",
	"in_de": "tradeType", "change_vol": "changeVolume",
	"change_ratio": "changeRatio", "after_share": "sharesAfterChange",
	"after_ratio": "ratioAfterChange", "avg_price": "averagePrice",
	"total_share": "totalShares", "begin_date": "beginDate",
	"close_date": "closeDate",
}

// paramBinding maps one camelCase input field onto its Tushare params key.
type paramBinding struct {
	inputName  string
	tushareKey string
}

var stockBasicParams = []paramBinding{
	{"exchange", "exchange"}, {"listStatus", "list_status"}, {"isHs", "is_hs"},
}

var tradeCalendarParams = []paramBinding{
	{"exchange", "exchange"}, {"startDate", "start_date"},
	{"endDate", "end_date"}, {"isOpen", "is_open"},
}

// mappedInput is the string-filtered shape shared by list-stocks and
// get-trade-calendar.
type mappedInput struct {
	Exchange   string `json:"exchange"`
	ListStatus string `json:"listStatus"`
	IsHs       string `json:"isHs"`
	StartDate  string `json:"startDate"`
	EndDate    string `json:"endDate"`
	IsOpen     *int   `json:"isOpen"`
}

// datedInput is the filter contract of the dated market-data actions.
type datedInput struct {
	TsCode    string `json:"tsCode"`
	TradeDate string `json:"tradeDate"`
	StartDate string `json:"startDate"`
	EndDate   string `json:"endDate"`
}

// shareholderTradesInput is the stk_holdertrade filter contract.
type shareholderTradesInput struct {
	TsCode           string `json:"tsCode"`
	AnnouncementDate string `json:"announcementDate"`
	StartDate        string `json:"startDate"`
	EndDate          string `json:"endDate"`
	TradeType        string `json:"tradeType"`
	HolderType       string `json:"holderType"`
}

func (input *mappedInput) trimStrings() {
	input.Exchange = strings.TrimSpace(input.Exchange)
	input.ListStatus = strings.TrimSpace(input.ListStatus)
	input.IsHs = strings.TrimSpace(input.IsHs)
	input.StartDate = strings.TrimSpace(input.StartDate)
	input.EndDate = strings.TrimSpace(input.EndDate)
}

func (input *datedInput) trimStrings() {
	input.TsCode = strings.TrimSpace(input.TsCode)
	input.TradeDate = strings.TrimSpace(input.TradeDate)
	input.StartDate = strings.TrimSpace(input.StartDate)
	input.EndDate = strings.TrimSpace(input.EndDate)
}

func (input *shareholderTradesInput) trimStrings() {
	input.TsCode = strings.TrimSpace(input.TsCode)
	input.AnnouncementDate = strings.TrimSpace(input.AnnouncementDate)
	input.StartDate = strings.TrimSpace(input.StartDate)
	input.EndDate = strings.TrimSpace(input.EndDate)
	input.TradeType = strings.TrimSpace(input.TradeType)
	input.HolderType = strings.TrimSpace(input.HolderType)
}

func decodeMappedInput(input []byte, params []paramBinding) (map[string]any, error) {
	var parsed mappedInput
	if err := decodeInput(input, &parsed); err != nil {
		return nil, err
	}
	parsed.trimStrings()
	requiresDateRange := false
	for _, binding := range params {
		if binding.inputName == "startDate" || binding.inputName == "endDate" {
			requiresDateRange = true
			break
		}
	}
	if requiresDateRange && (parsed.StartDate == "" || parsed.EndDate == "") {
		return nil, runner.Errorf(
			runner.ErrorValidation,
			"startDate and endDate are required", nil)
	}
	values := map[string]any{}
	for _, binding := range params {
		switch binding.inputName {
		case "exchange":
			if parsed.Exchange != "" {
				values[binding.tushareKey] = parsed.Exchange
			}
		case "listStatus":
			if parsed.ListStatus != "" {
				values[binding.tushareKey] = parsed.ListStatus
			}
		case "isHs":
			if parsed.IsHs != "" {
				values[binding.tushareKey] = parsed.IsHs
			}
		case "startDate":
			if parsed.StartDate != "" {
				values[binding.tushareKey] = parsed.StartDate
			}
		case "endDate":
			if parsed.EndDate != "" {
				values[binding.tushareKey] = parsed.EndDate
			}
		case "isOpen":
			if parsed.IsOpen != nil {
				values[binding.tushareKey] = *parsed.IsOpen
			}
		}
	}
	return values, nil
}

// assertBounded mirrors assertDatedMarketDataInput: tsCode, tradeDate, or
// both startDate and endDate must be present.
func (input datedInput) assertBounded() error {
	if input.TsCode != "" || input.TradeDate != "" ||
		(input.StartDate != "" && input.EndDate != "") {
		return nil
	}
	return runner.Errorf(
		runner.ErrorValidation,
		"this action requires tsCode, tradeDate, or startDate and endDate", nil)
}

func (input datedInput) emit(params map[string]any) {
	if input.TsCode != "" {
		params["ts_code"] = input.TsCode
	}
	if input.TradeDate != "" {
		params["trade_date"] = input.TradeDate
	}
	if input.StartDate != "" {
		params["start_date"] = input.StartDate
	}
	if input.EndDate != "" {
		params["end_date"] = input.EndDate
	}
}

// assertBounded mirrors validateShareholderTradeFilters: tsCode,
// announcementDate, or both startDate and endDate; a half-open date range is
// rejected.
func (input shareholderTradesInput) assertBounded() error {
	hasStart := input.StartDate != ""
	hasEnd := input.EndDate != ""
	bounded := input.TsCode != "" || input.AnnouncementDate != "" || (hasStart && hasEnd)
	if !bounded || hasStart != hasEnd {
		return runner.Errorf(
			runner.ErrorValidation,
			"get-shareholder-trades requires tsCode, announcementDate, or both startDate and endDate", nil)
	}
	return nil
}

func (input shareholderTradesInput) emit(params map[string]any) {
	if input.TsCode != "" {
		params["ts_code"] = input.TsCode
	}
	if input.AnnouncementDate != "" {
		params["ann_date"] = input.AnnouncementDate
	}
	if input.StartDate != "" {
		params["start_date"] = input.StartDate
	}
	if input.EndDate != "" {
		params["end_date"] = input.EndDate
	}
	if input.TradeType != "" {
		params["trade_type"] = input.TradeType
	}
	if input.HolderType != "" {
		params["holder_type"] = input.HolderType
	}
}
