package service

import (
	"context"
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/sqlexec"
	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/utils"
)

type cdcTablesEligibilityExecutor interface {
	QueryMulti(ctx context.Context, clusterID string, cmds []sqlexec.QueryCommand) ([]sqlexec.QueryResult, error)
}

var newCDCTablesEligibilityExecutor = func(
	resolver utils.AddressResolver,
	auth sqlexec.DBAuth,
	tlsProvider sqlexec.TLSConfigProvider,
) cdcTablesEligibilityExecutor {
	return sqlexec.NewDirectTiDBExecutor(resolver, auth, tlsProvider)
}

func (s *AgentService) QueryTiCDCTablesEligibility(
	ctx context.Context,
	req *apiv1.QueryTiCDCTablesEligibilityReq,
) (*apiv1.QueryTiCDCTablesEligibilityResp, error) {
	if req == nil {
		return nil, fmt.Errorf("request is nil")
	}

	logger := tilog.WithContextS(ctx).With("cluster_id", req.ClusterId)

	resp, err := s.queryTiCDCTablesEligibility(ctx, req.ClusterId, req.EncryptedUsername, req.EncryptedPassword)
	if err != nil {
		logTiCDCTablesQueryError(logger, err)
		return nil, err
	}
	return resp, nil
}

func (s *AgentService) queryTiCDCTablesEligibility(
	ctx context.Context,
	clusterID string,
	encryptedUsername []byte,
	encryptedPassword []byte,
) (*apiv1.QueryTiCDCTablesEligibilityResp, error) {
	resolver, auth, tlsProvider, err := s.decryptDBCredentials(encryptedUsername, encryptedPassword)
	if err != nil {
		return nil, err
	}
	exec := newCDCTablesEligibilityExecutor(resolver, auth, tlsProvider)
	out, err := exec.QueryMulti(ctx, clusterID, cdcTablesEligibilityQueryCommands())
	if err != nil {
		return nil, err
	}
	if len(out) != 2 {
		return nil, fmt.Errorf("unexpected result count %d, want 2", len(out))
	}

	eligibleTables, err := buildTableSchemaInfoFromQueryResult(out[0])
	if err != nil {
		return nil, fmt.Errorf("build eligible tables response: %w", err)
	}
	ineligibleTables, err := buildTableSchemaInfoFromQueryResult(out[1])
	if err != nil {
		return nil, fmt.Errorf("build ineligible tables response: %w", err)
	}

	return &apiv1.QueryTiCDCTablesEligibilityResp{
		EligibleTables:   eligibleTables,
		IneligibleTables: ineligibleTables,
	}, nil
}

func cdcTablesEligibilityQueryCommands() []sqlexec.QueryCommand {
	return []sqlexec.QueryCommand{
		&sqlexec.QueryCDCEligibleTablesCommand{},
		&sqlexec.QueryCDCIneligibleTablesCommand{},
	}
}

type cdcTablesQueryLogger interface {
	Errorf(template string, args ...any)
}

func logTiCDCTablesQueryError(logger cdcTablesQueryLogger, err error) {
	logSQLError(logger, "failed to query tables", err)
}

func buildTableSchemaInfoFromQueryResult(out sqlexec.QueryResult) (*apiv1.TableSchemaInfo, error) {
	resp := &apiv1.TableSchemaInfo{}
	for i, row := range out.Rows {
		if len(row) < 2 {
			return nil, fmt.Errorf("row %d has %d columns, want >= 2", i, len(row))
		}
		schema, err := toQueryString(row[0])
		if err != nil {
			return nil, fmt.Errorf("read table_schema at row %d: %w", i, err)
		}
		table, err := toQueryString(row[1])
		if err != nil {
			return nil, fmt.Errorf("read table_name at row %d: %w", i, err)
		}
		resp.TableSchemas = append(resp.TableSchemas, schema)
		resp.TableNames = append(resp.TableNames, table)
	}
	return resp, nil
}

func toQueryString(v any) (string, error) {
	if v == nil {
		return "", fmt.Errorf("value is nil")
	}
	switch t := v.(type) {
	case string:
		return t, nil
	case []byte:
		return string(t), nil
	default:
		return "", fmt.Errorf("unsupported type %T", v)
	}
}
