package main

import (
	"context"
	"crypto/tls"
	"database/sql"
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/go-sql-driver/mysql"
)

type migrateWorkflowAlertsArgs struct {
	mode          Mode
	outputDir     string
	allowBlockers bool
}

type migrationDBOpener func(context.Context) (*sql.DB, error)

func main() {
	os.Exit(runMigrateWorkflowAlertsCLI(context.Background(), os.Args[1:], os.Stdout, os.Stderr, openMigrationDB))
}

func runMigrateWorkflowAlertsCLI(ctx context.Context, args []string, stdout, stderr io.Writer, openDB migrationDBOpener) int {
	parsed, err := parseMigrateWorkflowAlertsArgs(args, stderr)
	if err != nil {
		fmt.Fprintln(stderr, err)
		return 2
	}

	db, err := openDB(ctx)
	if err != nil {
		fmt.Fprintf(stderr, "migrate-workflow-alerts-to-flows: failed to connect to database: %v\n", err)
		return 1
	}
	defer func() {
		if err := db.Close(); err != nil {
			fmt.Fprintf(stderr, "migrate-workflow-alerts-to-flows: failed to close database connection: %v\n", err)
		}
	}()

	return runMigrateWorkflowAlertsWithParsedArgs(ctx, db, parsed, stdout, stderr)
}

func runMigrateWorkflowAlertsToFlows(ctx context.Context, db *sql.DB, args []string, stdout, stderr io.Writer) int {
	parsed, err := parseMigrateWorkflowAlertsArgs(args, stderr)
	if err != nil {
		fmt.Fprintln(stderr, err)
		return 2
	}
	return runMigrateWorkflowAlertsWithParsedArgs(ctx, db, parsed, stdout, stderr)
}

func runMigrateWorkflowAlertsWithParsedArgs(ctx context.Context, db *sql.DB, parsed migrateWorkflowAlertsArgs, stdout, stderr io.Writer) int {
	report, err := Run(ctx, db, Options{
		Mode:             parsed.mode,
		OfficialPackRoot: officialPackRootForMode(parsed.mode),
	})
	if err != nil {
		fmt.Fprintf(stderr, "migrate-workflow-alerts-to-flows: %v\n", err)
		return 1
	}
	if err := WriteReportsWithOptions(report, parsed.outputDir, WriteReportsOptions{
		AllowBlockedApplyPack: parsed.allowBlockers,
	}); err != nil {
		fmt.Fprintf(stderr, "migrate-workflow-alerts-to-flows: %v\n", err)
		return 1
	}
	fmt.Fprintf(stdout, "migration reports written to %s\n", parsed.outputDir)
	if report.CutoverBlocked {
		if parsed.mode == ModeVerifyZero {
			fmt.Fprintf(stderr, "verify-zero failed by %d active WorkflowAlert dependency blocker(s); inspect migration_report.json and migration_report.md\n", report.Summary.BlockerCount)
			return 1
		}
		fmt.Fprintf(stderr, "cutover blocked by %d unsupported item(s); inspect migration_report.json and migration_report.md\n", report.Summary.BlockerCount)
		if !parsed.allowBlockers {
			return 1
		}
	}
	return 0
}

func officialPackRootForMode(mode Mode) string {
	if mode != ModeVerifyZero {
		return ""
	}
	return strings.TrimSpace(os.Getenv(officialAppPacksRootEnvVar))
}

func parseMigrateWorkflowAlertsArgs(args []string, stderr io.Writer) (migrateWorkflowAlertsArgs, error) {
	modeFromSubcommand := ""
	if len(args) > 0 && !strings.HasPrefix(args[0], "-") {
		modeFromSubcommand = strings.TrimSpace(args[0])
		args = args[1:]
	}

	fs := flag.NewFlagSet("migrate-workflow-alerts-to-flows", flag.ContinueOnError)
	fs.SetOutput(stderr)
	modeFlag := fs.String("mode", "", "Migration mode: inventory, dry-run, apply-pack, or verify-zero")
	outputDir := fs.String("output-dir", "", "Directory for migration reports and generated apply-pack output")
	allowBlockers := fs.Bool("allow-blockers", false, "Exit 0 even when the report marks cutover as blocked")
	if err := fs.Parse(args); err != nil {
		return migrateWorkflowAlertsArgs{}, err
	}
	if fs.NArg() != 0 {
		return migrateWorkflowAlertsArgs{}, fmt.Errorf("migrate-workflow-alerts-to-flows: unexpected arguments: %s", strings.Join(fs.Args(), " "))
	}
	modeRaw := strings.TrimSpace(*modeFlag)
	if modeFromSubcommand != "" {
		if modeRaw != "" && !strings.EqualFold(modeRaw, modeFromSubcommand) {
			return migrateWorkflowAlertsArgs{}, fmt.Errorf("migrate-workflow-alerts-to-flows: mode subcommand %q conflicts with --mode %q", modeFromSubcommand, modeRaw)
		}
		modeRaw = modeFromSubcommand
	}
	mode := Mode(modeRaw)
	if mode != ModeInventory && mode != ModeDryRun && mode != ModeApplyPack && mode != ModeVerifyZero {
		return migrateWorkflowAlertsArgs{}, fmt.Errorf("migrate-workflow-alerts-to-flows: mode must be inventory, dry-run, apply-pack, or verify-zero")
	}
	if strings.TrimSpace(*outputDir) == "" {
		return migrateWorkflowAlertsArgs{}, fmt.Errorf("migrate-workflow-alerts-to-flows: --output-dir is required")
	}
	if mode == ModeVerifyZero && *allowBlockers {
		return migrateWorkflowAlertsArgs{}, fmt.Errorf("migrate-workflow-alerts-to-flows: --allow-blockers cannot waive verify-zero")
	}
	return migrateWorkflowAlertsArgs{
		mode:          mode,
		outputDir:     strings.TrimSpace(*outputDir),
		allowBlockers: *allowBlockers,
	}, nil
}

func openMigrationDB(ctx context.Context) (*sql.DB, error) {
	dsn := strings.TrimSpace(os.Getenv("WORKFLOWALERT_MIGRATION_DATABASE_DSN"))
	if dsn == "" {
		host := os.Getenv("TIDB_HOST")
		port := os.Getenv("TIDB_PORT")
		user := os.Getenv("TIDB_USER")
		password := os.Getenv("TIDB_PASSWORD")
		database := os.Getenv("TIDB_DATABASE")
		if port == "" {
			port = "4000"
		}
		if database == "" {
			database = "nexuscrm"
		}

		tlsParam := ""
		tlsDisabled := os.Getenv("TIDB_TLS_DISABLE") == "1" || strings.EqualFold(os.Getenv("TIDB_TLS_DISABLE"), "true")
		if !tlsDisabled && host != "" && host != "127.0.0.1" && host != "localhost" {
			if err := mysql.RegisterTLSConfig("tidb", &tls.Config{
				MinVersion: tls.VersionTLS12,
				ServerName: host,
			}); err != nil && !strings.Contains(err.Error(), "key tidb already registered") {
				return nil, fmt.Errorf("register tidb TLS config: %w", err)
			}
			tlsParam = "&tls=tidb"
		}
		dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local%s",
			user, password, host, port, database, tlsParam)
	}

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, fmt.Errorf("open database: %w", err)
	}
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(10)
	db.SetConnMaxLifetime(5 * time.Minute)
	db.SetConnMaxIdleTime(3 * time.Minute)
	if err := db.PingContext(ctx); err != nil {
		_ = db.Close()
		return nil, fmt.Errorf("ping database: %w", err)
	}
	return db, nil
}
