package graphql

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strings"

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

type Reader struct {
	options *readers.ReaderOptions
}

func NewReader(options *readers.ReaderOptions) *Reader {
	return &Reader{
		options: options,
	}
}

func (r *Reader) ReadDatabase() (*models.Database, error) {
	if r.options.FilePath == "" {
		return nil, fmt.Errorf("file path is required for GraphQL reader")
	}

	content, err := os.ReadFile(r.options.FilePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read file: %w", err)
	}

	return r.parseGraphQL(string(content))
}

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 schemas found")
	}

	return db.Schemas[0], nil
}

func (r *Reader) ReadTable() (*models.Table, error) {
	schema, err := r.ReadSchema()
	if err != nil {
		return nil, err
	}

	if len(schema.Tables) == 0 {
		return nil, fmt.Errorf("no tables found")
	}

	return schema.Tables[0], nil
}

type parseContext struct {
	inType        bool
	inEnum        bool
	currentType   string
	typeLines     []string
	currentEnum   string
	enumLines     []string
	customScalars map[string]bool
}

func (r *Reader) parseGraphQL(content string) (*models.Database, error) {
	dbName := "database"
	if r.options.Metadata != nil {
		if name, ok := r.options.Metadata["name"].(string); ok {
			dbName = name
		}
	}

	db := models.InitDatabase(dbName)
	schema := models.InitSchema("public")

	ctx := &parseContext{
		customScalars: make(map[string]bool),
	}

	// First pass: collect custom scalars and enums
	scanner := bufio.NewScanner(strings.NewReader(content))
	scalarRegex := regexp.MustCompile(`^\s*scalar\s+(\w+)`)
	enumRegex := regexp.MustCompile(`^\s*enum\s+(\w+)\s*\{`)
	closingBraceRegex := regexp.MustCompile(`^\s*\}`)

	for scanner.Scan() {
		line := scanner.Text()
		trimmed := strings.TrimSpace(line)

		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			continue
		}

		if matches := scalarRegex.FindStringSubmatch(trimmed); matches != nil {
			ctx.customScalars[matches[1]] = true
			continue
		}

		if matches := enumRegex.FindStringSubmatch(trimmed); matches != nil {
			ctx.inEnum = true
			ctx.currentEnum = matches[1]
			ctx.enumLines = []string{}
			continue
		}

		if closingBraceRegex.MatchString(trimmed) && ctx.inEnum {
			r.parseEnum(ctx.currentEnum, ctx.enumLines, schema)
			// Add enum name to custom scalars for type detection
			ctx.customScalars[ctx.currentEnum] = true
			ctx.inEnum = false
			ctx.currentEnum = ""
			ctx.enumLines = nil
			continue
		}

		if ctx.inEnum {
			ctx.enumLines = append(ctx.enumLines, line)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("scanner error: %w", err)
	}

	// Second pass: parse types
	scanner = bufio.NewScanner(strings.NewReader(content))
	typeRegex := regexp.MustCompile(`^\s*type\s+(\w+)\s*\{`)
	ctx.inType = false
	ctx.inEnum = false

	for scanner.Scan() {
		line := scanner.Text()
		trimmed := strings.TrimSpace(line)

		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			continue
		}

		if matches := typeRegex.FindStringSubmatch(trimmed); matches != nil {
			ctx.inType = true
			ctx.currentType = matches[1]
			ctx.typeLines = []string{}
			continue
		}

		if closingBraceRegex.MatchString(trimmed) && ctx.inType {
			if err := r.parseType(ctx.currentType, ctx.typeLines, schema, ctx); err != nil {
				return nil, fmt.Errorf("failed to parse type %s: %w", ctx.currentType, err)
			}
			ctx.inType = false
			ctx.currentType = ""
			ctx.typeLines = nil
			continue
		}

		if ctx.inType {
			ctx.typeLines = append(ctx.typeLines, line)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("scanner error: %w", err)
	}

	db.Schemas = []*models.Schema{schema}

	// Third pass: detect and create relationships
	if err := r.detectAndCreateRelationships(schema, ctx); err != nil {
		return nil, fmt.Errorf("failed to create relationships: %w", err)
	}

	return db, nil
}

type fieldInfo struct {
	name          string
	typeName      string
	isArray       bool
	isNullable    bool
	innerNullable bool
}

func (r *Reader) parseType(typeName string, lines []string, schema *models.Schema, ctx *parseContext) error {
	table := models.InitTable(typeName, schema.Name)
	table.Metadata = make(map[string]any)

	// Store field info for relationship detection
	relationFields := make(map[string]*fieldInfo)

	fieldRegex := regexp.MustCompile(`^\s*(\w+)\s*:\s*(\[)?(\w+)(!)?(\])?(!)?\s*`)

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			continue
		}

		matches := fieldRegex.FindStringSubmatch(trimmed)
		if matches == nil {
			continue
		}

		fieldName := matches[1]
		hasOpenBracket := matches[2] == "["
		baseType := matches[3]
		innerNonNull := matches[4] == "!"
		hasCloseBracket := matches[5] == "]"
		outerNonNull := matches[6] == "!"

		isArray := hasOpenBracket && hasCloseBracket

		// Determine if this is a scalar or a relation
		if r.isScalarType(baseType, ctx) {
			// This is a scalar field
			column := models.InitColumn(fieldName, table.Name, schema.Name)
			column.Type = r.graphQLTypeToSQL(baseType, fieldName, typeName)

			if isArray {
				// Array of scalars: use array type
				column.Type += "[]"
				column.NotNull = outerNonNull
			} else {
				column.NotNull = !isArray && innerNonNull
			}

			// Check if this is a primary key (convention: field named "id")
			if fieldName == "id" {
				column.IsPrimaryKey = true
				column.AutoIncrement = true
			}

			table.Columns[fieldName] = column
		} else {
			// This is a relation field - store for later processing
			relationFields[fieldName] = &fieldInfo{
				name:          fieldName,
				typeName:      baseType,
				isArray:       isArray,
				isNullable:    !innerNonNull && !isArray,
				innerNullable: !innerNonNull && isArray,
			}
		}
	}

	// Store relation fields in table metadata for relationship detection
	if len(relationFields) > 0 {
		table.Metadata["relationFields"] = relationFields
	}

	schema.Tables = append(schema.Tables, table)
	return nil
}

func (r *Reader) parseEnum(enumName string, lines []string, schema *models.Schema) {
	enum := models.InitEnum(enumName, schema.Name)

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if trimmed == "" || strings.HasPrefix(trimmed, "#") {
			continue
		}
		// Enum values are simple identifiers
		enum.Values = append(enum.Values, trimmed)
	}

	schema.Enums = append(schema.Enums, enum)
}
