package migrate

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/dapicom-ai/omnipus/pkg/migrate/internal"
	"github.com/dapicom-ai/omnipus/pkg/migrate/sources/openclaw"
)

type (
	Options        = internal.Options
	Operation      = internal.Operation
	ActionType     = internal.ActionType
	Action         = internal.Action
	Result         = internal.Result
	HandlerFactory = internal.HandlerFactory
)

const (
	ActionCopy          = internal.ActionCopy
	ActionSkip          = internal.ActionSkip
	ActionBackup        = internal.ActionBackup
	ActionConvertConfig = internal.ActionConvertConfig
	ActionCreateDir     = internal.ActionCreateDir
	ActionMergeConfig   = internal.ActionMergeConfig
)

type MigrateInstance struct {
	options  Options
	handlers map[string]Operation
}

func NewMigrateInstance(opts Options) *MigrateInstance {
	instance := &MigrateInstance{
		options:  opts,
		handlers: make(map[string]Operation),
	}

	openclaw_handler, err := openclaw.NewOpenclawHandler(opts)
	if err == nil {
		instance.Register(openclaw_handler.GetSourceName(), openclaw_handler)
	}

	return instance
}

func (m *MigrateInstance) Register(moduleName string, module Operation) {
	m.handlers[moduleName] = module
}

func (m *MigrateInstance) getCurrentHandler() (Operation, error) {
	source := m.options.Source
	if source == "" {
		source = "openclaw"
	}
	handler, ok := m.handlers[source]
	if !ok {
		return nil, fmt.Errorf("Source '%s' not found", source)
	}
	return handler, nil
}

func (m *MigrateInstance) Run(opts Options) (*Result, error) {
	handler, err := m.getCurrentHandler()
	if err != nil {
		return nil, err
	}

	if opts.ConfigOnly && opts.WorkspaceOnly {
		return nil, fmt.Errorf("--config-only and --workspace-only are mutually exclusive")
	}

	if opts.Refresh {
		opts.WorkspaceOnly = true
	}

	sourceHome, err := handler.GetSourceHome()
	if err != nil {
		return nil, err
	}

	targetHome, err := internal.ResolveTargetHome(opts.TargetHome)
	if err != nil {
		return nil, err
	}

	if _, err = os.Stat(sourceHome); os.IsNotExist(err) {
		return nil, fmt.Errorf("Source installation not found at %s", sourceHome)
	}

	actions, warnings, err := m.Plan(opts, sourceHome, targetHome)
	if err != nil {
		return nil, err
	}

	fmt.Println("Migrating from Source to Omnipus")
	fmt.Printf("  Source:      %s\n", sourceHome)
	fmt.Printf("  Target: %s\n", targetHome)
	fmt.Println()

	if opts.DryRun {
		PrintPlan(actions, warnings)
		return &Result{Warnings: warnings}, nil
	}

	if !opts.Force {
		PrintPlan(actions, warnings)
		if !Confirm() {
			fmt.Println("Aborted.")
			return &Result{Warnings: warnings}, nil
		}
		fmt.Println()
	}

	result := m.Execute(actions, sourceHome, targetHome)
	result.Warnings = warnings
	return result, nil
}

func (m *MigrateInstance) Plan(opts Options, sourceHome, targetHome string) ([]Action, []string, error) {
	var actions []Action
	var warnings []string
	handler, err := m.getCurrentHandler()
	if err != nil {
		return nil, nil, err
	}

	force := opts.Force || opts.Refresh

	if !opts.WorkspaceOnly {
		configPath, err := handler.GetSourceConfigFile()
		if err != nil {
			if opts.ConfigOnly {
				return nil, nil, err
			}
			warnings = append(warnings, fmt.Sprintf("Config migration skipped: %v", err))
		} else {
			actions = append(actions, Action{
				Type:        ActionConvertConfig,
				Source:      configPath,
				Target:      filepath.Join(targetHome, "config.json"),
				Description: "convert Source config to Omnipus format",
			})
		}
	}

	if !opts.ConfigOnly {
		srcWorkspace, err := handler.GetSourceWorkspace()
		if err != nil {
			return nil, nil, fmt.Errorf("getting source workspace: %w", err)
		}
		dstWorkspace := internal.ResolveWorkspace(targetHome)

		if _, err := os.Stat(srcWorkspace); err == nil {
			wsActions, err := internal.PlanWorkspaceMigration(srcWorkspace, dstWorkspace,
				handler.GetMigrateableFiles(),
				handler.GetMigrateableDirs(),
				force)
			if err != nil {
				return nil, nil, fmt.Errorf("planning workspace migration: %w", err)
			}
			actions = append(actions, wsActions...)
		} else {
			warnings = append(warnings, "Source workspace directory not found, skipping workspace migration")
		}
	}

	return actions, warnings, nil
}

func (m *MigrateInstance) Execute(actions []Action, sourceHome, targetHome string) *Result {
	result := &Result{}
	handler, err := m.getCurrentHandler()
	if err != nil {
		return result
	}

	for _, action := range actions {
		switch action.Type {
		case ActionConvertConfig:
			if err := handler.ExecuteConfigMigration(action.Source, action.Target); err != nil {
				result.Errors = append(result.Errors, fmt.Errorf("config migration: %w", err))
				fmt.Printf("  ✗ Config migration failed: %v\n", err)
			} else {
				result.ConfigMigrated = true
				fmt.Printf("  ✓ Converted config: %s\n", action.Target)
			}
		case ActionCreateDir:
			if err := os.MkdirAll(action.Target, 0o755); err != nil {
				result.Errors = append(result.Errors, err)
			} else {
				result.DirsCreated++
			}
		case ActionBackup:
			bakPath := action.Target + ".bak"
			if err := internal.CopyFile(action.Target, bakPath); err != nil {
				result.Errors = append(result.Errors, fmt.Errorf("backup %s: %w", action.Target, err))
				fmt.Printf("  ✗ Backup failed: %s\n", action.Target)
				continue
			}
			result.BackupsCreated++
			fmt.Printf(
				"  ✓ Backed up %s -> %s.bak\n",
				filepath.Base(action.Target),
				filepath.Base(action.Target),
			)

			if err := os.MkdirAll(filepath.Dir(action.Target), 0o755); err != nil {
				result.Errors = append(result.Errors, err)
				continue
			}
			if err := internal.CopyFile(action.Source, action.Target); err != nil {
				result.Errors = append(result.Errors, fmt.Errorf("copy %s: %w", action.Source, err))
				fmt.Printf("  ✗ Copy failed: %s\n", action.Source)
			} else {
				result.FilesCopied++
				fmt.Printf("  ✓ Copied %s\n", internal.RelPath(action.Source, sourceHome))
			}
		case ActionCopy:
			if err := os.MkdirAll(filepath.Dir(action.Target), 0o755); err != nil {
				result.Errors = append(result.Errors, err)
				continue
			}
			if err := internal.CopyFile(action.Source, action.Target); err != nil {
				result.Errors = append(result.Errors, fmt.Errorf("copy %s: %w", action.Source, err))
				fmt.Printf("  ✗ Copy failed: %s\n", action.Source)
			} else {
				result.FilesCopied++
				fmt.Printf("  ✓ Copied %s\n", internal.RelPath(action.Source, sourceHome))
			}
		case ActionSkip:
			result.FilesSkipped++
		}
	}

	return result
}

func Confirm() bool {
	fmt.Print("Proceed with migration? (y/n): ")
	var response string
	fmt.Scanln(&response)
	return strings.ToLower(strings.TrimSpace(response)) == "y"
}

func (m *MigrateInstance) PrintSummary(result *Result) {
	fmt.Println()
	parts := []string{}
	if result.FilesCopied > 0 {
		parts = append(parts, fmt.Sprintf("%d files copied", result.FilesCopied))
	}
	if result.ConfigMigrated {
		parts = append(parts, "1 config converted")
	}
	if result.BackupsCreated > 0 {
		parts = append(parts, fmt.Sprintf("%d backups created", result.BackupsCreated))
	}
	if result.FilesSkipped > 0 {
		parts = append(parts, fmt.Sprintf("%d files skipped", result.FilesSkipped))
	}

	if len(parts) > 0 {
		fmt.Printf("Migration complete! %s.\n", strings.Join(parts, ", "))
	} else {
		fmt.Println("Migration complete! No actions taken.")
	}

	if len(result.Errors) > 0 {
		fmt.Println()
		fmt.Printf("%d errors occurred:\n", len(result.Errors))
		for _, e := range result.Errors {
			fmt.Printf("  - %v\n", e)
		}
	}
}

func PrintPlan(actions []Action, warnings []string) {
	fmt.Println("Planned actions:")
	copies := 0
	skips := 0
	backups := 0
	configCount := 0

	for _, action := range actions {
		switch action.Type {
		case ActionConvertConfig:
			fmt.Printf("  [config]  %s -> %s\n", action.Source, action.Target)
			configCount++
		case ActionCopy:
			fmt.Printf("  [copy]    %s\n", filepath.Base(action.Source))
			copies++
		case ActionBackup:
			fmt.Printf("  [backup]  %s (exists, will backup and overwrite)\n", filepath.Base(action.Target))
			backups++
			copies++
		case ActionSkip:
			if action.Description != "" {
				fmt.Printf("  [skip]    %s (%s)\n", filepath.Base(action.Source), action.Description)
			}
			skips++
		case ActionCreateDir:
			fmt.Printf("  [mkdir]   %s\n", action.Target)
		}
	}

	if len(warnings) > 0 {
		fmt.Println()
		fmt.Println("Warnings:")
		for _, w := range warnings {
			fmt.Printf("  - %s\n", w)
		}
	}

	fmt.Println()
	fmt.Printf("%d files to copy, %d configs to convert, %d backups needed, %d skipped\n",
		copies, configCount, backups, skips)
}
