package sqldir

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"

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

// Reader implements the readers.Reader interface for SQL script directories
type Reader struct {
	options *readers.ReaderOptions
}

// NewReader creates a new SQL directory reader
func NewReader(options *readers.ReaderOptions) *Reader {
	return &Reader{
		options: options,
	}
}

// ReadDatabase reads all SQL scripts from a directory into a Database
func (r *Reader) ReadDatabase() (*models.Database, error) {
	if r.options.FilePath == "" {
		return nil, fmt.Errorf("directory path is required")
	}

	// Check if directory exists
	info, err := os.Stat(r.options.FilePath)
	if err != nil {
		return nil, fmt.Errorf("failed to access directory: %w", err)
	}
	if !info.IsDir() {
		return nil, fmt.Errorf("path is not a directory: %s", r.options.FilePath)
	}

	// Read scripts from directory
	scripts, err := r.readScripts()
	if err != nil {
		return nil, fmt.Errorf("failed to read scripts: %w", err)
	}

	// Get schema name from metadata or use default
	schemaName := "public"
	if name, ok := r.options.Metadata["schema_name"].(string); ok && name != "" {
		schemaName = name
	}

	// Create schema with scripts
	schema := &models.Schema{
		Name:    schemaName,
		Scripts: scripts,
	}

	// Get database name from metadata or use default
	dbName := "database"
	if name, ok := r.options.Metadata["database_name"].(string); ok && name != "" {
		dbName = name
	}

	// Create database with schema
	database := &models.Database{
		Name:    dbName,
		Schemas: []*models.Schema{schema},
	}

	// Set back-reference
	schema.RefDatabase = database

	return database, nil
}

// ReadSchema reads all SQL scripts from a directory into a Schema
func (r *Reader) ReadSchema() (*models.Schema, error) {
	db, err := r.ReadDatabase()
	if err != nil {
		return nil, err
	}
	if len(db.Schemas) == 0 {
		return nil, fmt.Errorf("no schema found")
	}
	return db.Schemas[0], nil
}

// ReadTable is not applicable for SQL script directories
func (r *Reader) ReadTable() (*models.Table, error) {
	return nil, fmt.Errorf("ReadTable is not supported for SQL script directories")
}

// readScripts recursively scans the directory for SQL files and parses them into Script models
func (r *Reader) readScripts() ([]*models.Script, error) {
	var scripts []*models.Script

	// Regular expression to parse filename: {priority}{sep}{sequence}{sep}{name}.sql or .pgsql
	// Separator can be underscore (_) or hyphen (-)
	// Example: 1_001_create_users.sql -> priority=1, sequence=001, name=create_users
	// Example: 2_005_add_indexes.pgsql -> priority=2, sequence=005, name=add_indexes
	// Example: 10-10-create-newid.pgsql -> priority=10, sequence=10, name=create-newid
	pattern := regexp.MustCompile(`^(\d+)[_-](\d+)[_-](.+)\.(sql|pgsql)$`)

	err := filepath.WalkDir(r.options.FilePath, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// Don't process directories as files (WalkDir still descends into them recursively)
		if d.IsDir() {
			return nil
		}

		// Skip symlinks
		info, err := d.Info()
		if err != nil {
			return err
		}
		if info.Mode()&os.ModeSymlink != 0 {
			return nil
		}

		// Get filename
		filename := d.Name()

		// Match against pattern
		matches := pattern.FindStringSubmatch(filename)
		if matches == nil {
			// Skip files that don't match the pattern
			return nil
		}

		// Parse priority
		priority, err := strconv.Atoi(matches[1])
		if err != nil {
			return fmt.Errorf("invalid priority in filename %s: %w", filename, err)
		}

		// Parse sequence
		sequence, err := strconv.ParseUint(matches[2], 10, 64)
		if err != nil {
			return fmt.Errorf("invalid sequence in filename %s: %w", filename, err)
		}

		// Extract name
		name := matches[3]

		// Read SQL content
		content, err := os.ReadFile(path)
		if err != nil {
			return fmt.Errorf("failed to read file %s: %w", path, err)
		}
		sql, err := assetloader.ProcessEmbedDirectives(path, string(content))
		if err != nil {
			return err
		}

		// Get relative path from base directory
		relPath, err := filepath.Rel(r.options.FilePath, path)
		if err != nil {
			relPath = path
		}

		// Create Script model
		script := models.InitScript(name)
		script.Description = fmt.Sprintf("SQL script from %s", relPath)
		script.SQL = sql
		script.Priority = priority
		script.Sequence = uint(sequence)
		script.Metadata[assetloader.ScriptSourcePathMetadataKey] = path

		scripts = append(scripts, script)

		return nil
	})
	if err != nil {
		return nil, err
	}

	return scripts, nil
}
