package sqlite

import (
	"context"
	"fmt"
	"strings"
)

type RouteDecisionLog struct {
	ID                    int64
	RequestID             string
	LogicalGroupID        string
	PublicModel           string
	UserKey               string
	ConversationKey       string
	StickyKey             string
	StickyKeyType         string
	StickyHit             bool
	SelectedRouteID       string
	SelectedShadowGroupID string
	FallbackUsed          bool
	ErrorClass            string
	UpstreamStatus        int
	LatencyMS             int
	CreatedAt             string
}

type RouteDecisionLogFilter struct {
	RequestID       string
	LogicalGroupID  string
	PublicModel     string
	SelectedRouteID string
	StickyKey       string
	Limit           int
}

type RouteDecisionLogsRepo struct {
	db execQuerier
}

func newRouteDecisionLogsRepo(db execQuerier) *RouteDecisionLogsRepo {
	return &RouteDecisionLogsRepo{db: db}
}

func (r *RouteDecisionLogsRepo) Create(ctx context.Context, row RouteDecisionLog) (int64, error) {
	row, err := normalizeRouteDecisionLog(row)
	if err != nil {
		return 0, err
	}

	result, err := r.db.ExecContext(
		ctx,
		`INSERT INTO route_decision_logs (
			request_id,
			logical_group_id,
			public_model,
			user_key,
			conversation_key,
			sticky_key,
			sticky_key_type,
			sticky_hit,
			selected_route_id,
			selected_shadow_group_id,
			fallback_used,
			error_class,
			upstream_status,
			latency_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		row.RequestID,
		row.LogicalGroupID,
		row.PublicModel,
		row.UserKey,
		row.ConversationKey,
		row.StickyKey,
		row.StickyKeyType,
		boolToSQLiteInt(row.StickyHit),
		row.SelectedRouteID,
		row.SelectedShadowGroupID,
		boolToSQLiteInt(row.FallbackUsed),
		row.ErrorClass,
		row.UpstreamStatus,
		row.LatencyMS,
	)
	if err != nil {
		return 0, fmt.Errorf("insert route decision log %q: %w", row.RequestID, err)
	}

	id, err := result.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("read inserted route decision log id for %q: %w", row.RequestID, err)
	}
	return id, nil
}

func (r *RouteDecisionLogsRepo) ListRecent(ctx context.Context, filter RouteDecisionLogFilter) ([]RouteDecisionLog, error) {
	clauses := make([]string, 0, 5)
	args := make([]any, 0, 6)

	if requestID := strings.TrimSpace(filter.RequestID); requestID != "" {
		clauses = append(clauses, "request_id = ?")
		args = append(args, requestID)
	}
	if logicalGroupID := strings.TrimSpace(filter.LogicalGroupID); logicalGroupID != "" {
		clauses = append(clauses, "logical_group_id = ?")
		args = append(args, logicalGroupID)
	}
	if publicModel := strings.TrimSpace(filter.PublicModel); publicModel != "" {
		clauses = append(clauses, "public_model = ?")
		args = append(args, publicModel)
	}
	if selectedRouteID := strings.TrimSpace(filter.SelectedRouteID); selectedRouteID != "" {
		clauses = append(clauses, "selected_route_id = ?")
		args = append(args, selectedRouteID)
	}
	if stickyKey := strings.TrimSpace(filter.StickyKey); stickyKey != "" {
		clauses = append(clauses, "sticky_key = ?")
		args = append(args, stickyKey)
	}

	query := `SELECT id, request_id, logical_group_id, public_model, user_key, conversation_key, sticky_key, sticky_key_type, sticky_hit, selected_route_id, selected_shadow_group_id, fallback_used, error_class, upstream_status, latency_ms, created_at
		FROM route_decision_logs`
	if len(clauses) > 0 {
		query += " WHERE " + strings.Join(clauses, " AND ")
	}
	query += " ORDER BY id DESC LIMIT ?"
	args = append(args, normalizeRouteLogListLimit(filter.Limit))

	rows, err := r.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("list route decision logs: %w", err)
	}
	defer rows.Close()

	items := make([]RouteDecisionLog, 0)
	for rows.Next() {
		var (
			item         RouteDecisionLog
			stickyHit    int
			fallbackUsed int
		)
		if err := rows.Scan(
			&item.ID,
			&item.RequestID,
			&item.LogicalGroupID,
			&item.PublicModel,
			&item.UserKey,
			&item.ConversationKey,
			&item.StickyKey,
			&item.StickyKeyType,
			&stickyHit,
			&item.SelectedRouteID,
			&item.SelectedShadowGroupID,
			&fallbackUsed,
			&item.ErrorClass,
			&item.UpstreamStatus,
			&item.LatencyMS,
			&item.CreatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan route decision log: %w", err)
		}
		item.StickyHit = stickyHit != 0
		item.FallbackUsed = fallbackUsed != 0
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate route decision logs: %w", err)
	}
	return items, nil
}

func normalizeRouteDecisionLog(row RouteDecisionLog) (RouteDecisionLog, error) {
	row.RequestID = strings.TrimSpace(row.RequestID)
	row.LogicalGroupID = strings.TrimSpace(row.LogicalGroupID)
	row.PublicModel = strings.TrimSpace(row.PublicModel)
	row.UserKey = strings.TrimSpace(row.UserKey)
	row.ConversationKey = strings.TrimSpace(row.ConversationKey)
	row.StickyKey = strings.TrimSpace(row.StickyKey)
	row.StickyKeyType = strings.TrimSpace(row.StickyKeyType)
	row.SelectedRouteID = strings.TrimSpace(row.SelectedRouteID)
	row.SelectedShadowGroupID = strings.TrimSpace(row.SelectedShadowGroupID)
	row.ErrorClass = strings.TrimSpace(row.ErrorClass)

	switch {
	case row.RequestID == "":
		return RouteDecisionLog{}, fmt.Errorf("request_id is required")
	case row.LogicalGroupID == "":
		return RouteDecisionLog{}, fmt.Errorf("logical_group_id is required")
	case row.PublicModel == "":
		return RouteDecisionLog{}, fmt.Errorf("public_model is required")
	case row.SelectedRouteID == "":
		return RouteDecisionLog{}, fmt.Errorf("selected_route_id is required")
	case row.SelectedShadowGroupID == "":
		return RouteDecisionLog{}, fmt.Errorf("selected_shadow_group_id is required")
	case row.UpstreamStatus < 0:
		return RouteDecisionLog{}, fmt.Errorf("upstream_status must be >= 0")
	case row.LatencyMS < 0:
		return RouteDecisionLog{}, fmt.Errorf("latency_ms must be >= 0")
	}

	return row, nil
}

func boolToSQLiteInt(value bool) int {
	if value {
		return 1
	}
	return 0
}
