package sqlexec

import (
	"context"
	"fmt"
	"sort"

	"github.com/jackc/pgx/v5"

	"git.warky.dev/wdevs/relspecgo/pkg/assetloader"
	"git.warky.dev/wdevs/relspecgo/pkg/models"
	"git.warky.dev/wdevs/relspecgo/pkg/pgsql"
	"git.warky.dev/wdevs/relspecgo/pkg/writers"
)

// Writer implements the writers.Writer interface for executing SQL scripts
type Writer struct {
	options *writers.WriterOptions
}

// NewWriter creates a new SQL executor writer
func NewWriter(options *writers.WriterOptions) *Writer {
	return &Writer{
		options: options,
	}
}

// Options returns the writer options (useful for reading execution results)
func (w *Writer) Options() *writers.WriterOptions {
	return w.options
}

// WriteDatabase executes all scripts from all schemas in the database
func (w *Writer) WriteDatabase(db *models.Database) error {
	if db == nil {
		return fmt.Errorf("database is nil")
	}

	// Get connection string from metadata
	connString, ok := w.options.Metadata["connection_string"].(string)
	if !ok || connString == "" {
		return fmt.Errorf("connection_string is required in writer metadata")
	}

	// Connect to database
	ctx := context.Background()
	conn, err := pgsql.Connect(ctx, connString, "writer-sqlexec")
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}
	defer conn.Close(ctx)

	// Execute scripts from all schemas
	for _, schema := range db.Schemas {
		if err := w.executeScripts(ctx, conn, schema.Scripts); err != nil {
			return fmt.Errorf("failed to execute scripts from schema %s: %w", schema.Name, err)
		}
	}

	return nil
}

// WriteSchema executes all scripts from a single schema
func (w *Writer) WriteSchema(schema *models.Schema) error {
	if schema == nil {
		return fmt.Errorf("schema is nil")
	}

	// Get connection string from metadata
	connString, ok := w.options.Metadata["connection_string"].(string)
	if !ok || connString == "" {
		return fmt.Errorf("connection_string is required in writer metadata")
	}

	// Connect to database
	ctx := context.Background()
	conn, err := pgsql.Connect(ctx, connString, "writer-sqlexec")
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}
	defer conn.Close(ctx)

	// Execute scripts
	if err := w.executeScripts(ctx, conn, schema.Scripts); err != nil {
		return fmt.Errorf("failed to execute scripts: %w", err)
	}

	return nil
}

// WriteTable is not applicable for SQL script execution
func (w *Writer) WriteTable(table *models.Table) error {
	return fmt.Errorf("WriteTable is not supported for SQL script execution")
}

// executeScripts executes scripts in Priority, Sequence, then Name order
func (w *Writer) executeScripts(ctx context.Context, conn *pgx.Conn, scripts []*models.Script) error {
	if len(scripts) == 0 {
		return nil
	}

	// Check if we should ignore errors
	ignoreErrors := false
	if val, ok := w.options.Metadata["ignore_errors"].(bool); ok {
		ignoreErrors = val
	}

	// Track failed scripts and execution counts
	var failedScripts []struct {
		name     string
		priority int
		sequence uint
		err      error
	}
	successCount := 0
	totalCount := 0

	// Sort scripts by Priority (ascending), Sequence (ascending), then Name (ascending)
	sortedScripts := make([]*models.Script, len(scripts))
	copy(sortedScripts, scripts)
	sort.Slice(sortedScripts, func(i, j int) bool {
		if sortedScripts[i].Priority != sortedScripts[j].Priority {
			return sortedScripts[i].Priority < sortedScripts[j].Priority
		}
		if sortedScripts[i].Sequence != sortedScripts[j].Sequence {
			return sortedScripts[i].Sequence < sortedScripts[j].Sequence
		}
		return sortedScripts[i].Name < sortedScripts[j].Name
	})

	// Execute each script in order
	for _, script := range sortedScripts {
		if script.SQL == "" {
			continue
		}

		totalCount++
		fmt.Printf("Executing script: %s (Priority=%d, Sequence=%d)\n",
			script.Name, script.Priority, script.Sequence)

		// Execute the SQL script
		sql, err := processEmbedDirectives(script)
		if err != nil {
			if ignoreErrors {
				fmt.Printf("⚠ Error preparing %s: %v (continuing due to --ignore-errors)\n", script.Name, err)
				failedScripts = append(failedScripts, struct {
					name     string
					priority int
					sequence uint
					err      error
				}{
					name:     script.Name,
					priority: script.Priority,
					sequence: script.Sequence,
					err:      err,
				})
				continue
			}
			return fmt.Errorf("script %s (Priority=%d, Sequence=%d): %w",
				script.Name, script.Priority, script.Sequence, err)
		}

		_, err = conn.Exec(ctx, sql)
		if err != nil {
			if ignoreErrors {
				fmt.Printf("⚠ Error executing %s: %v (continuing due to --ignore-errors)\n", script.Name, err)
				failedScripts = append(failedScripts, struct {
					name     string
					priority int
					sequence uint
					err      error
				}{
					name:     script.Name,
					priority: script.Priority,
					sequence: script.Sequence,
					err:      err,
				})
				continue
			}
			return fmt.Errorf("script %s (Priority=%d, Sequence=%d): %w",
				script.Name, script.Priority, script.Sequence, err)
		}

		successCount++
		fmt.Printf("✓ Successfully executed: %s\n", script.Name)
	}

	// Store execution results in metadata for caller
	w.options.Metadata["execution_total"] = totalCount
	w.options.Metadata["execution_success"] = successCount
	w.options.Metadata["execution_failed"] = len(failedScripts)

	// Print summary of failed scripts if any
	if len(failedScripts) > 0 {
		fmt.Printf("\n⚠ Failed Scripts Summary (%d failed):\n", len(failedScripts))
		for i, failed := range failedScripts {
			fmt.Printf("  %d. %s (Priority=%d, Sequence=%d)\n     Error: %v\n",
				i+1, failed.name, failed.priority, failed.sequence, failed.err)
		}
	}

	return nil
}

func processEmbedDirectives(script *models.Script) (string, error) {
	sqlPath, _ := script.Metadata[assetloader.ScriptSourcePathMetadataKey].(string)
	if sqlPath == "" {
		return script.SQL, nil
	}
	return assetloader.ProcessEmbedDirectives(sqlPath, script.SQL)
}
