package postgres

import (
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/jackc/pgx/v5/pgtype"

	"git.toki-labs.com/toki/alt/packages/domain/backtest"
	"git.toki-labs.com/toki/alt/packages/domain/market"
	"git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres/sqlc"
)

func validateDecimal(val string) error {
	if val == "" {
		return fmt.Errorf("empty decimal value")
	}
	if _, err := strconv.ParseFloat(val, 64); err != nil {
		return fmt.Errorf("invalid decimal format: %s", val)
	}
	return nil
}

func stringToNumeric(val string) (pgtype.Numeric, error) {
	var num pgtype.Numeric
	if err := validateDecimal(val); err != nil {
		return num, err
	}
	if err := num.Scan(val); err != nil {
		return num, fmt.Errorf("failed to scan numeric: %w", err)
	}
	return num, nil
}

func numericToString(num pgtype.Numeric) (string, error) {
	if !num.Valid {
		return "0", nil
	}
	val, err := num.Value()
	if err != nil {
		return "", err
	}
	switch v := val.(type) {
	case string:
		return v, nil
	case []byte:
		return string(v), nil
	default:
		return fmt.Sprintf("%v", v), nil
	}
}

func mapInstrumentToParams(inst market.Instrument) (sqlc.UpsertInstrumentParams, error) {
	provBytes, err := json.Marshal(inst.ProviderSymbols)
	if err != nil {
		return sqlc.UpsertInstrumentParams{}, fmt.Errorf("failed to marshal provider symbols: %w", err)
	}
	if provBytes == nil {
		provBytes = []byte("{}")
	}
	return sqlc.UpsertInstrumentParams{
		ID:              string(inst.ID),
		Market:          string(inst.Market),
		Venue:           string(inst.Venue),
		Symbol:          inst.Symbol,
		Name:            inst.Name,
		Currency:        string(inst.Currency),
		ProviderSymbols: provBytes,
		AssetType:       string(inst.AssetType),
	}, nil
}

func mapRowToInstrument(row sqlc.Instrument) (market.Instrument, error) {
	var provSymbols map[string]string
	if len(row.ProviderSymbols) > 0 {
		if err := json.Unmarshal(row.ProviderSymbols, &provSymbols); err != nil {
			return market.Instrument{}, fmt.Errorf("failed to unmarshal provider symbols: %w", err)
		}
	}
	if provSymbols == nil {
		provSymbols = make(map[string]string)
	}
	return market.Instrument{
		ID:              market.InstrumentID(row.ID),
		Market:          market.Market(row.Market),
		Venue:           market.Venue(row.Venue),
		Symbol:          row.Symbol,
		Name:            row.Name,
		Currency:        market.Currency(row.Currency),
		AssetType:       market.AssetType(row.AssetType),
		ProviderSymbols: provSymbols,
	}, nil
}

func mapBarToParams(bar market.Bar) (sqlc.UpsertBarParams, error) {
	open, err := stringToNumeric(bar.Open.Amount.Value)
	if err != nil {
		return sqlc.UpsertBarParams{}, fmt.Errorf("invalid open amount: %w", err)
	}
	high, err := stringToNumeric(bar.High.Amount.Value)
	if err != nil {
		return sqlc.UpsertBarParams{}, fmt.Errorf("invalid high amount: %w", err)
	}
	low, err := stringToNumeric(bar.Low.Amount.Value)
	if err != nil {
		return sqlc.UpsertBarParams{}, fmt.Errorf("invalid low amount: %w", err)
	}
	closePrice, err := stringToNumeric(bar.Close.Amount.Value)
	if err != nil {
		return sqlc.UpsertBarParams{}, fmt.Errorf("invalid close amount: %w", err)
	}
	volume, err := stringToNumeric(bar.Volume.Amount.Value)
	if err != nil {
		return sqlc.UpsertBarParams{}, fmt.Errorf("invalid volume amount: %w", err)
	}

	return sqlc.UpsertBarParams{
		InstrumentID: string(bar.InstrumentID),
		Timeframe:    string(bar.Timeframe),
		Timestamp:    pgtype.Timestamptz{Time: bar.Timestamp, Valid: !bar.Timestamp.IsZero()},
		Open:         open,
		High:         high,
		Low:          low,
		Close:        closePrice,
		Volume:       volume,
	}, nil
}

func mapRowToBar(row sqlc.Bar, currency market.Currency) (market.Bar, error) {
	openStr, err := numericToString(row.Open)
	if err != nil {
		return market.Bar{}, err
	}
	highStr, err := numericToString(row.High)
	if err != nil {
		return market.Bar{}, err
	}
	lowStr, err := numericToString(row.Low)
	if err != nil {
		return market.Bar{}, err
	}
	closeStr, err := numericToString(row.Close)
	if err != nil {
		return market.Bar{}, err
	}
	volumeStr, err := numericToString(row.Volume)
	if err != nil {
		return market.Bar{}, err
	}

	return market.Bar{
		InstrumentID: market.InstrumentID(row.InstrumentID),
		Timeframe:    market.Timeframe(row.Timeframe),
		Timestamp:    row.Timestamp.Time,
		Open: market.Price{
			Currency: currency,
			Amount:   market.Decimal{Value: openStr},
		},
		High: market.Price{
			Currency: currency,
			Amount:   market.Decimal{Value: highStr},
		},
		Low: market.Price{
			Currency: currency,
			Amount:   market.Decimal{Value: lowStr},
		},
		Close: market.Price{
			Currency: currency,
			Amount:   market.Decimal{Value: closeStr},
		},
		Volume: market.Quantity{
			Amount: market.Decimal{Value: volumeStr},
		},
	}, nil
}

func mapRunToParams(run backtest.Run) (sqlc.UpsertRunParams, error) {
	return sqlc.UpsertRunParams{
		ID:         string(run.ID),
		StrategyID: string(run.Spec.StrategyID),
		Market:     string(run.Spec.Market),
		Timeframe:  string(run.Spec.Timeframe),
		FromTime:   pgtype.Timestamptz{Time: run.Spec.From, Valid: !run.Spec.From.IsZero()},
		ToTime:     pgtype.Timestamptz{Time: run.Spec.To, Valid: !run.Spec.To.IsZero()},
		Status:     string(run.Status),
		CreatedAt:  pgtype.Timestamptz{Time: run.CreatedAt, Valid: !run.CreatedAt.IsZero()},
		UpdatedAt:  pgtype.Timestamptz{Time: run.UpdatedAt, Valid: !run.UpdatedAt.IsZero()},
	}, nil
}

func mapRowToRun(row sqlc.BacktestRun) (backtest.Run, error) {
	return backtest.Run{
		ID: backtest.RunID(row.ID),
		Spec: backtest.RunSpec{
			StrategyID: backtest.StrategyID(row.StrategyID),
			Market:     market.Market(row.Market),
			Timeframe:  market.Timeframe(row.Timeframe),
			From:       row.FromTime.Time,
			To:         row.ToTime.Time,
		},
		Status:    backtest.RunStatus(row.Status),
		CreatedAt: row.CreatedAt.Time,
		UpdatedAt: row.UpdatedAt.Time,
	}, nil
}

func mapResultToParams(res backtest.Result) (sqlc.UpsertResultParams, error) {
	startingCashAmount, err := stringToNumeric(res.StartingCash.Amount.Value)
	if err != nil {
		return sqlc.UpsertResultParams{}, fmt.Errorf("invalid starting cash amount: %w", err)
	}
	endingEquityAmount, err := stringToNumeric(res.EndingEquity.Amount.Value)
	if err != nil {
		return sqlc.UpsertResultParams{}, fmt.Errorf("invalid ending equity amount: %w", err)
	}

	var tradesBytes []byte
	if res.Trades == nil {
		tradesBytes = []byte("[]")
	} else {
		tradesBytes, err = json.Marshal(res.Trades)
		if err != nil {
			return sqlc.UpsertResultParams{}, fmt.Errorf("failed to marshal trades: %w", err)
		}
	}

	var positionsBytes []byte
	if res.Positions == nil {
		positionsBytes = []byte("[]")
	} else {
		positionsBytes, err = json.Marshal(res.Positions)
		if err != nil {
			return sqlc.UpsertResultParams{}, fmt.Errorf("failed to marshal positions: %w", err)
		}
	}

	// Summary defaults to "0" total return when callers persist a result without a
	// computed summary so the NOT NULL numeric column never receives an empty value.
	totalReturnValue := res.Summary.TotalReturn.Value
	if totalReturnValue == "" {
		totalReturnValue = "0"
	}
	totalReturn, err := stringToNumeric(totalReturnValue)
	if err != nil {
		return sqlc.UpsertResultParams{}, fmt.Errorf("invalid total return: %w", err)
	}

	var equityCurveBytes []byte
	if res.EquityCurve == nil {
		equityCurveBytes = []byte("[]")
	} else {
		equityCurveBytes, err = json.Marshal(res.EquityCurve)
		if err != nil {
			return sqlc.UpsertResultParams{}, fmt.Errorf("failed to marshal equity curve: %w", err)
		}
	}

	return sqlc.UpsertResultParams{
		RunID:                string(res.RunID),
		StartingCashCurrency: string(res.StartingCash.Currency),
		StartingCashAmount:   startingCashAmount,
		EndingEquityCurrency: string(res.EndingEquity.Currency),
		EndingEquityAmount:   endingEquityAmount,
		TotalReturn:          totalReturn,
		TradeCount:           int32(res.Summary.TradeCount),
		Trades:               tradesBytes,
		Positions:            positionsBytes,
		EquityCurve:          equityCurveBytes,
	}, nil
}

func mapRowToResult(row sqlc.BacktestResult) (backtest.Result, error) {
	startingCashAmountStr, err := numericToString(row.StartingCashAmount)
	if err != nil {
		return backtest.Result{}, fmt.Errorf("failed to parse starting cash amount: %w", err)
	}
	endingEquityAmountStr, err := numericToString(row.EndingEquityAmount)
	if err != nil {
		return backtest.Result{}, fmt.Errorf("failed to parse ending equity amount: %w", err)
	}

	var trades []backtest.TradeSummary
	if len(row.Trades) > 0 {
		if err := json.Unmarshal(row.Trades, &trades); err != nil {
			return backtest.Result{}, fmt.Errorf("failed to unmarshal trades: %w", err)
		}
	}

	var positions []backtest.PositionSummary
	if len(row.Positions) > 0 {
		if err := json.Unmarshal(row.Positions, &positions); err != nil {
			return backtest.Result{}, fmt.Errorf("failed to unmarshal positions: %w", err)
		}
	}

	totalReturnStr, err := numericToString(row.TotalReturn)
	if err != nil {
		return backtest.Result{}, fmt.Errorf("failed to parse total return: %w", err)
	}

	var equityCurve []backtest.EquityPoint
	if len(row.EquityCurve) > 0 {
		if err := json.Unmarshal(row.EquityCurve, &equityCurve); err != nil {
			return backtest.Result{}, fmt.Errorf("failed to unmarshal equity curve: %w", err)
		}
	}

	startingCash := market.Price{
		Currency: market.Currency(row.StartingCashCurrency),
		Amount:   market.Decimal{Value: startingCashAmountStr},
	}
	endingEquity := market.Price{
		Currency: market.Currency(row.EndingEquityCurrency),
		Amount:   market.Decimal{Value: endingEquityAmountStr},
	}

	return backtest.Result{
		RunID:        backtest.RunID(row.RunID),
		StartingCash: startingCash,
		EndingEquity: endingEquity,
		Trades:       trades,
		Positions:    positions,
		Summary: backtest.SummaryMetrics{
			StartingCash: startingCash,
			EndingEquity: endingEquity,
			TotalReturn:  market.Decimal{Value: totalReturnStr},
			TradeCount:   int(row.TradeCount),
		},
		EquityCurve: equityCurve,
	}, nil
}
