package sqlite

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
)

type LogicalGroupRoute struct {
	ID                  int64
	RouteID             string
	LogicalGroupID      string
	Name                string
	Status              string
	Priority            int
	Weight              int
	ShadowGroupID       string
	ShadowHostID        string
	UpstreamBaseURLHint string
	CooldownUntil       string
	CreatedAt           string
	UpdatedAt           string
}

type LogicalGroupRoutesRepo struct {
	db execQuerier
}

func newLogicalGroupRoutesRepo(db execQuerier) *LogicalGroupRoutesRepo {
	return &LogicalGroupRoutesRepo{db: db}
}

func (r *LogicalGroupRoutesRepo) Create(ctx context.Context, route LogicalGroupRoute) (int64, error) {
	route, err := normalizeLogicalGroupRoute(route)
	if err != nil {
		return 0, err
	}

	result, err := r.db.ExecContext(
		ctx,
		`INSERT INTO logical_group_routes (
			route_id,
			logical_group_id,
			name,
			status,
			priority,
			weight,
			shadow_group_id,
			shadow_host_id,
			upstream_base_url_hint,
			cooldown_until
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		route.RouteID,
		route.LogicalGroupID,
		route.Name,
		route.Status,
		route.Priority,
		route.Weight,
		route.ShadowGroupID,
		route.ShadowHostID,
		route.UpstreamBaseURLHint,
		route.CooldownUntil,
	)
	if err != nil {
		return 0, fmt.Errorf("insert logical group route %q: %w", route.RouteID, err)
	}

	id, err := result.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("read inserted logical group route id for %q: %w", route.RouteID, err)
	}
	return id, nil
}

func (r *LogicalGroupRoutesRepo) GetByRouteID(ctx context.Context, routeID string) (LogicalGroupRoute, error) {
	routeID = strings.TrimSpace(routeID)
	if routeID == "" {
		return LogicalGroupRoute{}, fmt.Errorf("route_id is required")
	}

	var route LogicalGroupRoute
	if err := r.db.QueryRowContext(
		ctx,
		`SELECT id, route_id, logical_group_id, name, status, priority, weight, shadow_group_id, shadow_host_id, upstream_base_url_hint, cooldown_until, created_at, updated_at
		FROM logical_group_routes
		WHERE route_id = ?`,
		routeID,
	).Scan(
		&route.ID,
		&route.RouteID,
		&route.LogicalGroupID,
		&route.Name,
		&route.Status,
		&route.Priority,
		&route.Weight,
		&route.ShadowGroupID,
		&route.ShadowHostID,
		&route.UpstreamBaseURLHint,
		&route.CooldownUntil,
		&route.CreatedAt,
		&route.UpdatedAt,
	); err != nil {
		return LogicalGroupRoute{}, err
	}
	return route, nil
}

func (r *LogicalGroupRoutesRepo) ListByLogicalGroupID(ctx context.Context, logicalGroupID string) ([]LogicalGroupRoute, error) {
	logicalGroupID = strings.TrimSpace(logicalGroupID)
	if logicalGroupID == "" {
		return nil, fmt.Errorf("logical_group_id is required")
	}

	rows, err := r.db.QueryContext(
		ctx,
		`SELECT id, route_id, logical_group_id, name, status, priority, weight, shadow_group_id, shadow_host_id, upstream_base_url_hint, cooldown_until, created_at, updated_at
		FROM logical_group_routes
		WHERE logical_group_id = ?
		ORDER BY priority ASC, id ASC`,
		logicalGroupID,
	)
	if err != nil {
		return nil, fmt.Errorf("list logical group routes for %q: %w", logicalGroupID, err)
	}
	defer rows.Close()

	routes := make([]LogicalGroupRoute, 0)
	for rows.Next() {
		var route LogicalGroupRoute
		if err := rows.Scan(
			&route.ID,
			&route.RouteID,
			&route.LogicalGroupID,
			&route.Name,
			&route.Status,
			&route.Priority,
			&route.Weight,
			&route.ShadowGroupID,
			&route.ShadowHostID,
			&route.UpstreamBaseURLHint,
			&route.CooldownUntil,
			&route.CreatedAt,
			&route.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan logical group route: %w", err)
		}
		routes = append(routes, route)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate logical group routes for %q: %w", logicalGroupID, err)
	}
	return routes, nil
}

func (r *LogicalGroupRoutesRepo) UpdateByRouteID(ctx context.Context, route LogicalGroupRoute) error {
	route, err := normalizeLogicalGroupRoute(route)
	if err != nil {
		return err
	}

	result, err := r.db.ExecContext(
		ctx,
		`UPDATE logical_group_routes
		SET logical_group_id = ?, name = ?, status = ?, priority = ?, weight = ?, shadow_group_id = ?, shadow_host_id = ?, upstream_base_url_hint = ?, cooldown_until = ?, updated_at = CURRENT_TIMESTAMP
		WHERE route_id = ?`,
		route.LogicalGroupID,
		route.Name,
		route.Status,
		route.Priority,
		route.Weight,
		route.ShadowGroupID,
		route.ShadowHostID,
		route.UpstreamBaseURLHint,
		route.CooldownUntil,
		route.RouteID,
	)
	if err != nil {
		return fmt.Errorf("update logical group route %q: %w", route.RouteID, err)
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("read updated logical group route rows for %q: %w", route.RouteID, err)
	}
	if affected == 0 {
		return fmt.Errorf("logical group route %q not found", route.RouteID)
	}
	return nil
}

func (r *LogicalGroupRoutesRepo) DeleteByRouteID(ctx context.Context, routeID string) error {
	routeID = strings.TrimSpace(routeID)
	if routeID == "" {
		return fmt.Errorf("route_id is required")
	}

	result, err := r.db.ExecContext(ctx, `DELETE FROM logical_group_routes WHERE route_id = ?`, routeID)
	if err != nil {
		return fmt.Errorf("delete logical group route %q: %w", routeID, err)
	}
	affected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("read deleted logical group route rows for %q: %w", routeID, err)
	}
	if affected == 0 {
		return fmt.Errorf("logical group route %q not found", routeID)
	}
	return nil
}

func (r *LogicalGroupRoutesRepo) GetByShadowBinding(ctx context.Context, shadowHostID, shadowGroupID string) (LogicalGroupRoute, error) {
	routes, err := r.ListByShadowBinding(ctx, shadowHostID, shadowGroupID)
	if err != nil {
		return LogicalGroupRoute{}, err
	}
	switch len(routes) {
	case 0:
		return LogicalGroupRoute{}, sql.ErrNoRows
	case 1:
		return routes[0], nil
	default:
		return LogicalGroupRoute{}, fmt.Errorf("multiple logical group routes match shadow binding %q/%q", strings.TrimSpace(shadowHostID), strings.TrimSpace(shadowGroupID))
	}
}

func (r *LogicalGroupRoutesRepo) ListByShadowBinding(ctx context.Context, shadowHostID, shadowGroupID string) ([]LogicalGroupRoute, error) {
	shadowHostID = strings.TrimSpace(shadowHostID)
	shadowGroupID = strings.TrimSpace(shadowGroupID)
	if shadowHostID == "" {
		return nil, fmt.Errorf("shadow_host_id is required")
	}
	if shadowGroupID == "" {
		return nil, fmt.Errorf("shadow_group_id is required")
	}

	rows, err := r.db.QueryContext(
		ctx,
		`SELECT id, route_id, logical_group_id, name, status, priority, weight, shadow_group_id, shadow_host_id, upstream_base_url_hint, cooldown_until, created_at, updated_at
		FROM logical_group_routes
		WHERE shadow_host_id = ? AND shadow_group_id = ?
		ORDER BY priority ASC, id ASC`,
		shadowHostID,
		shadowGroupID,
	)
	if err != nil {
		return nil, fmt.Errorf("list logical group routes by shadow binding %q/%q: %w", shadowHostID, shadowGroupID, err)
	}
	defer rows.Close()

	routes := make([]LogicalGroupRoute, 0, 4)
	for rows.Next() {
		var route LogicalGroupRoute
		if err := rows.Scan(
			&route.ID,
			&route.RouteID,
			&route.LogicalGroupID,
			&route.Name,
			&route.Status,
			&route.Priority,
			&route.Weight,
			&route.ShadowGroupID,
			&route.ShadowHostID,
			&route.UpstreamBaseURLHint,
			&route.CooldownUntil,
			&route.CreatedAt,
			&route.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan logical group route by shadow binding: %w", err)
		}
		routes = append(routes, route)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate logical group route by shadow binding %q/%q: %w", shadowHostID, shadowGroupID, err)
	}
	return routes, nil
}

func normalizeLogicalGroupRoute(route LogicalGroupRoute) (LogicalGroupRoute, error) {
	route.RouteID = strings.TrimSpace(route.RouteID)
	route.LogicalGroupID = strings.TrimSpace(route.LogicalGroupID)
	route.Name = strings.TrimSpace(route.Name)
	route.Status = strings.TrimSpace(route.Status)
	route.ShadowGroupID = strings.TrimSpace(route.ShadowGroupID)
	route.ShadowHostID = strings.TrimSpace(route.ShadowHostID)
	route.UpstreamBaseURLHint = strings.TrimSpace(route.UpstreamBaseURLHint)
	route.CooldownUntil = strings.TrimSpace(route.CooldownUntil)

	switch {
	case route.RouteID == "":
		return LogicalGroupRoute{}, fmt.Errorf("route_id is required")
	case route.LogicalGroupID == "":
		return LogicalGroupRoute{}, fmt.Errorf("logical_group_id is required")
	case route.Name == "":
		return LogicalGroupRoute{}, fmt.Errorf("name is required")
	case route.Status == "":
		return LogicalGroupRoute{}, fmt.Errorf("status is required")
	case route.ShadowGroupID == "":
		return LogicalGroupRoute{}, fmt.Errorf("shadow_group_id is required")
	case route.ShadowHostID == "":
		return LogicalGroupRoute{}, fmt.Errorf("shadow_host_id is required")
	}
	if route.Weight <= 0 {
		route.Weight = 100
	}
	return route, nil
}
