package financialmodelingprep

import (
	"context"
	"encoding/json"

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

// The custom-shaped handlers, mirroring the upstream executors exactly.

func handleSearchSymbols(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery(searchQueryKeys)
	if err != nil {
		return nil, err
	}
	if _, ok := query["query"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "query is required", nil)
	}
	payload, err := fmpGet(ctx, client, credentials, "/search-symbol", query)
	if err != nil {
		return nil, err
	}
	rows, err := requireArray(payload)
	if err != nil {
		return nil, err
	}
	results := make([]map[string]string, 0, len(rows))
	for _, row := range rows {
		record, err := requireObject(row, "payload[]")
		if err != nil {
			return nil, err
		}
		result := map[string]string{}
		for _, key := range []string{"symbol", "name", "currency", "stockExchange", "exchangeShortName"} {
			value, err := requireString(record, key)
			if err != nil {
				return nil, err
			}
			result[key] = value
		}
		results = append(results, result)
	}
	return json.Marshal(map[string]any{"results": results})
}

func screenerHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{
		"marketCapMoreThan", "marketCapLowerThan", "priceMoreThan", "priceLowerThan",
		"betaMoreThan", "betaLowerThan", "volumeMoreThan", "volumeLowerThan",
		"dividendMoreThan", "dividendLowerThan", "isEtf", "isFund",
		"sector", "industry", "country", "exchange", "limit",
	})
	if err != nil {
		return nil, err
	}
	return arrayRows(ctx, client, credentials, "/company-screener", query, "companies")
}

func directoryHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	listType, err := parsed.requiredString("type")
	if err != nil {
		return nil, err
	}
	path, ok := directoryEndpoints[listType]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "type is not supported", nil)
	}
	return arrayRows(ctx, client, credentials, path, map[string]string{}, "items")
}

func quoteHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	symbol, err := parsed.requiredString("symbol")
	if err != nil {
		return nil, err
	}
	quote, err := fetchQuote(ctx, client, credentials, symbol)
	if err != nil {
		return nil, err
	}
	return json.Marshal(map[string]any{"quote": quote})
}

func quoteShortHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol"})
	if err != nil {
		return nil, err
	}
	if _, ok := query["symbol"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "symbol is required", nil)
	}
	payload, err := fmpGet(ctx, client, credentials, "/quote-short", query)
	if err != nil {
		return nil, err
	}
	rows, err := requireArray(payload)
	if err != nil {
		return nil, err
	}
	if len(rows) == 0 {
		return nil, runner.Errorf(runner.ErrorUpstreamFailure, "payload[0] must be an object", nil)
	}
	quote, err := requireObject(rows[0], "payload[0]")
	if err != nil {
		return nil, err
	}
	return json.Marshal(map[string]any{"quote": quote})
}

func assetQuoteHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	assetType, err := parsed.requiredString("assetType")
	if err != nil {
		return nil, err
	}
	symbol, err := parsed.requiredString("symbol")
	if err != nil {
		return nil, err
	}
	path, ok := assetQuoteEndpoints[assetType]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "assetType is not supported", nil)
	}
	payload, err := fmpGet(ctx, client, credentials, path, map[string]string{})
	if err != nil {
		return nil, err
	}
	rows, err := requireArray(payload)
	if err != nil {
		return nil, err
	}
	var selected any
	if len(rows) > 0 {
		selected = rows[0]
	}
	for _, row := range rows {
		if record, isObject := row.(map[string]any); isObject {
			if rowSymbol, isString := record["symbol"].(string); isString && rowSymbol == symbol {
				selected = row
				break
			}
		}
	}
	quote, err := requireObject(selected, "payload[0]")
	if err != nil {
		return nil, err
	}
	return json.Marshal(map[string]any{"quote": quote})
}

func handleHistoricalPrices(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol", "from", "to"})
	if err != nil {
		return nil, err
	}
	symbol, ok := query["symbol"]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "symbol is required", nil)
	}
	payload, err := fmpGet(ctx, client, credentials, "/historical-price-eod/full", query)
	if err != nil {
		return nil, err
	}
	rows, err := requireArray(payload)
	if err != nil {
		return nil, err
	}
	historical := make([]map[string]any, 0, len(rows))
	for _, row := range rows {
		record, err := requireObject(row, "payload[]")
		if err != nil {
			return nil, err
		}
		price := map[string]any{}
		for _, key := range []string{"date", "open", "high", "low", "close", "adjClose", "volume"} {
			value, present := record[key]
			if !present {
				return nil, runner.Errorf(runner.ErrorUpstreamFailure, key+" must be present", nil)
			}
			price[key] = value
		}
		historical = append(historical, price)
	}
	return json.Marshal(map[string]any{"symbol": symbol, "historical": historical})
}

func intradayHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	interval, err := parsed.requiredString("interval")
	if err != nil {
		return nil, err
	}
	path, ok := intradayEndpoints[interval]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "interval is not supported", nil)
	}
	query, err := parsed.pickQuery([]string{"symbol", "from", "to"})
	if err != nil {
		return nil, err
	}
	if _, ok := query["symbol"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "symbol is required", nil)
	}
	return arrayRows(ctx, client, credentials, path, query, "prices")
}

func profileHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol"})
	if err != nil {
		return nil, err
	}
	if _, ok := query["symbol"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "symbol is required", nil)
	}
	payload, err := fmpGet(ctx, client, credentials, "/profile", query)
	if err != nil {
		return nil, err
	}
	rows, err := requireArray(payload)
	if err != nil {
		return nil, err
	}
	if len(rows) == 0 {
		return nil, runner.Errorf(runner.ErrorUpstreamFailure, "payload[0] must be an object", nil)
	}
	profile, err := requireObject(rows[0], "payload[0]")
	if err != nil {
		return nil, err
	}
	return json.Marshal(map[string]any{"profile": profile})
}

func moversHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	listType, err := parsed.requiredString("type")
	if err != nil {
		return nil, err
	}
	path, ok := map[string]string{
		"gainers": "/biggest-gainers", "losers": "/biggest-losers", "actives": "/most-actives",
	}[listType]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "type must be gainers, losers, or actives", nil)
	}
	payload, err := fmpGet(ctx, client, credentials, path, map[string]string{})
	if err != nil {
		return nil, err
	}
	rows, err := requireArray(payload)
	if err != nil {
		return nil, err
	}
	movers := make([]map[string]any, 0, len(rows))
	for _, row := range rows {
		record, err := requireObject(row, "payload[]")
		if err != nil {
			return nil, err
		}
		quote, err := normalizeQuote(record)
		if err != nil {
			return nil, err
		}
		movers = append(movers, quote)
	}
	return json.Marshal(map[string]any{"movers": movers})
}

func newsHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	newsType, err := parsed.requiredString("type")
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbols", "page", "limit"})
	if err != nil {
		return nil, err
	}
	endpoints := newsLatestEndpoints
	if query["symbols"] != "" {
		endpoints = newsSearchEndpoints
	}
	path, ok := endpoints[newsType]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "type is not supported", nil)
	}
	return arrayRows(ctx, client, credentials, path, query, "news")
}

func calendarHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	calendarType, err := parsed.requiredString("type")
	if err != nil {
		return nil, err
	}
	path, ok := calendarEndpoints[calendarType]
	if !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "type is not supported", nil)
	}
	query, err := parsed.pickQuery([]string{"from", "to"})
	if err != nil {
		return nil, err
	}
	return arrayRows(ctx, client, credentials, path, query, "events")
}

func indicatorsHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"name", "from", "to"})
	if err != nil {
		return nil, err
	}
	if _, ok := query["name"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "name is required", nil)
	}
	return arrayRows(ctx, client, credentials, "/economic-indicators", query, "indicators")
}

func estimatesHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol", "period", "page", "limit"})
	if err != nil {
		return nil, err
	}
	if _, ok := query["symbol"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "symbol is required", nil)
	}
	return arrayRows(ctx, client, credentials, "/analyst-estimates", query, "estimates")
}

func insiderTradesHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol", "reportingCik", "transactionType", "page", "limit"})
	if err != nil {
		return nil, err
	}
	if query["symbol"] == "" && query["reportingCik"] == "" && query["transactionType"] == "" {
		return arrayRows(ctx, client, credentials, "/insider-trading/latest",
			map[string]string{"page": query["page"], "limit": query["limit"]}, "trades")
	}
	return arrayRows(ctx, client, credentials, "/insider-trading/search", query, "trades")
}

func congressionalHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	chamber, err := parsed.requiredString("chamber")
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol", "name", "page", "limit"})
	if err != nil {
		return nil, err
	}
	endpoint := "/" + chamber + "-latest"
	if query["symbol"] != "" || query["name"] != "" {
		endpoint = "/" + chamber + "-trades"
		if query["name"] != "" {
			endpoint = "/" + chamber + "-trades-by-name"
		}
	}
	return arrayRows(ctx, client, credentials, endpoint, query, "trades")
}

func secFilingsHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"from", "to", "page", "limit"})
	if err != nil {
		return nil, err
	}
	symbol, err := parsed.optionalString("symbol")
	if err != nil {
		return nil, err
	}
	cik, err := parsed.optionalString("cik")
	if err != nil {
		return nil, err
	}
	formType, err := parsed.optionalString("formType")
	if err != nil {
		return nil, err
	}
	endpoint := "/latest-filings"
	switch {
	case symbol != "":
		endpoint = "/sec-filings-search/symbol"
		query["symbol"] = symbol
	case cik != "":
		endpoint = "/sec-filings-search/cik"
		query["cik"] = cik
	case formType != "":
		endpoint = "/sec-filings-search/form-type"
		query["formType"] = formType
	}
	return arrayRows(ctx, client, credentials, endpoint, query, "filings")
}

func technicalIndicatorHandler(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials, input json.RawMessage) (json.RawMessage, error) {
	parsed, err := decodeInput(input)
	if err != nil {
		return nil, err
	}
	indicator, err := parsed.requiredString("indicator")
	if err != nil {
		return nil, err
	}
	query, err := parsed.pickQuery([]string{"symbol", "periodLength", "timeframe", "from", "to"})
	if err != nil {
		return nil, err
	}
	if _, ok := query["symbol"]; !ok {
		return nil, runner.Errorf(runner.ErrorValidation, "symbol is required", nil)
	}
	return arrayRows(ctx, client, credentials, "/technical-indicators/"+indicator, query, "values")
}

// normalizeQuote mirrors the upstream compactObject projection: symbol is
// required, every other field is omitted when absent.
func normalizeQuote(record map[string]any) (map[string]any, error) {
	symbol, err := requireString(record, "symbol")
	if err != nil {
		return nil, err
	}
	quote := map[string]any{"symbol": symbol}
	for _, key := range []string{
		"name", "price", "change", "changesPercentage", "dayLow", "dayHigh",
		"yearHigh", "yearLow", "marketCap", "volume", "avgVolume", "exchange",
		"open", "previousClose", "timestamp",
	} {
		if value, present := record[key]; present && value != nil {
			quote[key] = value
		}
	}
	return quote, nil
}
