package config

import (
	"fmt"
	"os"
	"strconv"
	"strings"
)

// LoadMarkdown decodes a Markdown config file with YAML frontmatter
// (R-014). The file format is:
//
//	---
//	listen_addr: 127.0.0.1:9999
//	node_capacity:
//	  cpu: 4
//	  memory_mb: 8192
//	db_path: /tmp/orca/test.db
//	---
//
//	body prose (ignored)
//
// The frontmatter parser is a minimal hand-rolled key:value parser
// (no new dependencies; gopkg.in/yaml.v3 is not in go.mod). It supports
// flat scalar keys and one level of nested mapping (for node_capacity).
// The Markdown body after the closing "---" is ignored.
//
// The returned *Config is the same struct the HCL loader produces, so
// downstream consumers are unchanged.
func LoadMarkdown(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read config %s: %w", path, err)
	}
	return parseFrontmatter(string(data), path)
}

// LoadMarkdownYAML decodes a bare YAML file (no Markdown body) using the
// same minimal frontmatter parser. .yaml/.yml files are routed here by
// the dispatcher.
func LoadMarkdownYAML(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read config %s: %w", path, err)
	}
	// Treat the whole file as the frontmatter block (no surrounding ---).
	return parseFrontmatterBlock(string(data), path)
}

func parseFrontmatter(content, path string) (*Config, error) {
	block, ok := extractFrontmatter(content)
	if !ok {
		// No frontmatter delimiters: treat whole file as a bare block.
		return parseFrontmatterBlock(content, path)
	}
	return parseFrontmatterBlock(block, path)
}

// extractFrontmatter returns the YAML block between the first pair of
// "---" delimiters and whether a frontmatter block was present.
func extractFrontmatter(content string) (string, bool) {
	trimmed := strings.TrimLeft(content, "\r\n\t ")
	if !strings.HasPrefix(trimmed, "---") {
		return "", false
	}
	// Skip the opening delimiter line.
	rest := trimmed[3:]
	rest = strings.TrimLeft(rest, "\r\n")
	// Find the closing delimiter line.
	idx := strings.Index(rest, "\n---")
	if idx < 0 {
		return "", false
	}
	return rest[:idx], true
}

// parseFrontmatterBlock parses a minimal YAML-ish block into *Config.
// Supported shapes:
//
//	key: value
//	node_capacity:
//	  cpu: 4
//	  memory_mb: 8192
//
// Comments (# ...) and blank lines are ignored. Quoted scalar values
// ("..." or '...') are unwrapped. No flow collections, anchors, or
// multi-line strings are supported — by design, to avoid adding a YAML
// dependency for this small config surface.
func parseFrontmatterBlock(block, path string) (*Config, error) {
	cfg := &Config{}
	var inCapacity bool

	lines := strings.Split(block, "\n")
	for lineNo, raw := range lines {
		line := stripComment(raw)
		if strings.TrimSpace(line) == "" {
			continue
		}

		indent := countIndent(line)
		trimmed := strings.TrimSpace(line)

		// A top-level key (no leading indent).
		if indent == 0 {
			inCapacity = false
			key, val, ok := splitKV(trimmed)
			if !ok {
				continue
			}
			if val == "" {
				// key with no value → nested mapping header (e.g. node_capacity:)
				if key == "node_capacity" {
					cfg.NodeCapacity = &CapacityConfig{}
					inCapacity = true
				}
				continue
			}
			applyScalar(cfg, key, val, path, lineNo)
			continue
		}

		// Indented line under a nested mapping.
		if inCapacity && cfg.NodeCapacity != nil {
			key, val, hasVal := splitKV(trimmed)
			if !hasVal {
				continue
			}
			switch key {
			case "cpu":
				if n, err := strconv.Atoi(strings.TrimSpace(val)); err == nil {
					cfg.NodeCapacity.CPU = n
				}
			case "memory_mb":
				if n, err := strconv.Atoi(strings.TrimSpace(val)); err == nil {
					cfg.NodeCapacity.MemoryMB = n
				}
			}
		}
	}
	return cfg, nil
}

func applyScalar(cfg *Config, key, val, path string, lineNo int) {
	val = strings.TrimSpace(val)
	switch key {
	case "db_path":
		cfg.DBPath = unquote(val)
	case "listen_addr":
		cfg.ListenAddr = unquote(val)
	case "ca_path":
		cfg.CAPath = unquote(val)
	case "server_cert_path":
		cfg.ServerCertPath = unquote(val)
	case "server_key_path":
		cfg.ServerKeyPath = unquote(val)
	}
	_ = path
	_ = lineNo
}

func splitKV(s string) (key, val string, ok bool) {
	idx := strings.Index(s, ":")
	if idx < 0 {
		return "", "", false
	}
	key = strings.TrimSpace(s[:idx])
	val = strings.TrimSpace(s[idx+1:])
	if key == "" {
		return "", "", false
	}
	return key, val, true
}

func countIndent(s string) int {
	n := 0
	for _, r := range s {
		if r == ' ' || r == '\t' {
			n++
			continue
		}
		break
	}
	return n
}

func stripComment(s string) string {
	// Strip inline comments not inside quotes. Minimal: only strip
	// when the '#' is preceded by whitespace or at line start.
	inSingle := false
	inDouble := false
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch c {
		case '\'':
			if !inDouble {
				inSingle = !inSingle
			}
		case '"':
			if !inSingle {
				inDouble = !inDouble
			}
		case '#':
			if !inSingle && !inDouble {
				if i == 0 || s[i-1] == ' ' || s[i-1] == '\t' {
					return s[:i]
				}
			}
		}
	}
	return s
}

func unquote(s string) string {
	if len(s) >= 2 {
		if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') {
			return s[1 : len(s)-1]
		}
	}
	return s
}
