package migrate

import (
	"context"
	"fmt"
	"log"
	"os/exec"
	"strings"

	"github.com/tidbcloud/controlplane9/internal/config"
	"github.com/tidbcloud/controlplane9/internal/verbose"
)

// Dumper extracts schema and data from the staging database.
type Dumper struct {
	// StagingHost, StagingPort, StagingUser, StagingPass are parsed from the DSN or provided directly.
	StagingHost string
	StagingPort string
	StagingUser string
	StagingPass string

	// CommandRunner allows injecting a mock for testing.
	CommandRunner func(cmd *exec.Cmd) ([]byte, error)
}

// DumpSchema runs mysqldump --no-data to extract DDL from the staging database.
func (d *Dumper) DumpSchema(ctx context.Context, dbName string) ([]byte, error) {
	args := d.mysqldumpArgs(dbName)
	args = append(args, "--no-data", "--routines", "--triggers")

	cmd := exec.CommandContext(ctx, "mysqldump", args...)
	log.Printf("migrate: dumping schema for %s from staging", dbName)

	output, err := d.runCommand(cmd)
	if err != nil {
		return nil, fmt.Errorf("mysqldump schema for %s: %w\n%s", dbName, err, string(output))
	}
	return output, nil
}

// DumpTables runs mysqldump for specific tables to extract seed data.
func (d *Dumper) DumpTables(ctx context.Context, dbName string, tables []string) ([]byte, error) {
	if len(tables) == 0 {
		return nil, nil
	}

	args := d.mysqldumpArgs(dbName)
	args = append(args, tables...)

	cmd := exec.CommandContext(ctx, "mysqldump", args...)
	log.Printf("migrate: dumping seed tables for %s: %v", dbName, tables)

	output, err := d.runCommand(cmd)
	if err != nil {
		return nil, fmt.Errorf("mysqldump tables for %s: %w\n%s", dbName, err, string(output))
	}
	return output, nil
}

// DumpFullData runs mysqldump with full data for a database.
func (d *Dumper) DumpFullData(ctx context.Context, dbName string) ([]byte, error) {
	args := d.mysqldumpArgs(dbName)

	cmd := exec.CommandContext(ctx, "mysqldump", args...)
	log.Printf("migrate: dumping full data for %s from staging", dbName)

	output, err := d.runCommand(cmd)
	if err != nil {
		return nil, fmt.Errorf("mysqldump full for %s: %w\n%s", dbName, err, string(output))
	}
	return output, nil
}

func (d *Dumper) mysqldumpArgs(dbName string) []string {
	return []string{
		"--protocol=tcp",
		"-h", d.StagingHost,
		"-P", d.StagingPort,
		"-u", d.StagingUser,
		dbName,
	}
}

func (d *Dumper) runCommand(cmd *exec.Cmd) ([]byte, error) {
	// Pass password via environment to avoid leaking in process listings and verbose logs.
	if d.StagingPass != "" {
		cmd.Env = append(cmd.Environ(), "MYSQL_PWD="+d.StagingPass)
	}
	if d.CommandRunner != nil {
		return d.CommandRunner(cmd)
	}
	return verbose.RunCommand(cmd)
}

// ResolveSeedMode determines the effective seed mode for a service.
func ResolveSeedMode(seedFlag string, seedCfg config.SeedConfig, service string) config.SeedMode {
	// Flag overrides config.
	switch seedFlag {
	case "true", "config-tables":
		return config.SeedConfigTables
	case "full":
		return config.SeedFull
	}

	// Check per-service override — if tables are specified, use config-tables.
	if _, ok := seedCfg.Overrides[service]; ok {
		return config.SeedConfigTables
	}

	// Use configured default.
	if seedCfg.Default != "" {
		return seedCfg.Default
	}

	return config.SeedSchemaOnly
}

// SeedTables returns the list of tables to seed for a service.
func SeedTables(seedCfg config.SeedConfig, service string) []string {
	if entry, ok := seedCfg.Overrides[service]; ok {
		return entry.Tables
	}
	return nil
}

// PortForwardArgs returns kubectl port-forward arguments for accessing the staging DB.
func PortForwardArgs(kubeconfig, namespace, svcName, localPort, remotePort string) []string {
	args := []string{"port-forward"}
	if kubeconfig != "" {
		args = append(args, "--kubeconfig", kubeconfig)
	}
	if namespace != "" {
		args = append(args, "-n", namespace)
	}
	args = append(args, fmt.Sprintf("svc/%s", svcName), fmt.Sprintf("%s:%s", localPort, remotePort))
	return args
}

// ParseDSN extracts host, port, user, password from a Go MySQL DSN.
// Format: user:pass@tcp(host:port)/dbname
func ParseDSN(dsn string) (host, port, user, pass string, err error) {
	// Strip mysql:// prefix if present.
	dsn = strings.TrimPrefix(dsn, "mysql://")

	atIdx := strings.LastIndex(dsn, "@")
	if atIdx < 0 {
		return "", "", "", "", fmt.Errorf("invalid DSN: no @ separator")
	}

	userPass := dsn[:atIdx]
	hostPart := dsn[atIdx+1:]

	u, p, ok := strings.Cut(userPass, ":")
	if ok {
		user, pass = u, p
	} else {
		user = userPass
	}

	hostPart = strings.TrimPrefix(hostPart, "tcp(")
	hostPart, _, _ = strings.Cut(hostPart, "/")
	hostPart = strings.TrimSuffix(hostPart, ")")

	h, pt, ok := strings.Cut(hostPart, ":")
	if !ok {
		return "", "", "", "", fmt.Errorf("invalid DSN: no port")
	}
	host, port = h, pt

	return host, port, user, pass, nil
}
