package main

import (
	"bufio"
	"fmt"
	"os"
	"slices"
	"strings"
)

type Document struct {
	Preamble []string
	Sections []*Section
}

type Section struct {
	Name  string
	Lines []Line
}

type Line struct {
	Raw      string
	Key      string
	Value    string
	IsKV     bool
	Disabled bool
}

func ParseConfigFile(path string) (*Document, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	return ParseConfig(string(data))
}

func ParseConfig(contents string) (*Document, error) {
	doc := &Document{}
	var current *Section

	scanner := bufio.NewScanner(strings.NewReader(contents))
	for scanner.Scan() {
		raw := scanner.Text()
		trimmed := strings.TrimSpace(raw)
		if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
			name := strings.TrimSpace(trimmed[1 : len(trimmed)-1])
			current = &Section{Name: name}
			doc.Sections = append(doc.Sections, current)
			continue
		}

		line := parseLine(raw)
		if current == nil {
			doc.Preamble = append(doc.Preamble, raw)
			continue
		}
		current.Lines = append(current.Lines, line)
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return doc, nil
}

func parseLine(raw string) Line {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" || strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, ";") {
		return Line{Raw: raw}
	}

	disabled := false
	parseTarget := trimmed
	if strings.HasPrefix(parseTarget, "#") || strings.HasPrefix(parseTarget, ";") {
		disabled = true
		parseTarget = strings.TrimSpace(parseTarget[1:])
	}

	key, value, ok := strings.Cut(parseTarget, "=")
	if !ok {
		return Line{Raw: raw}
	}

	return Line{
		Raw:      raw,
		Key:      strings.TrimSpace(strings.ToLower(key)),
		Value:    strings.TrimSpace(value),
		IsKV:     true,
		Disabled: disabled,
	}
}

func (d *Document) ShareSections() []*Section {
	var shares []*Section
	for _, section := range d.Sections {
		if !strings.EqualFold(section.Name, "global") {
			shares = append(shares, section)
		}
	}
	return shares
}

func (d *Document) Section(name string) *Section {
	for _, section := range d.Sections {
		if strings.EqualFold(section.Name, name) {
			return section
		}
	}
	return nil
}

func (d *Document) DeleteSection(name string) bool {
	for i, section := range d.Sections {
		if strings.EqualFold(section.Name, name) {
			d.Sections = append(d.Sections[:i], d.Sections[i+1:]...)
			return true
		}
	}
	return false
}

func (d *Document) UpsertSection(section *Section) {
	for i, existing := range d.Sections {
		if strings.EqualFold(existing.Name, section.Name) {
			d.Sections[i] = section
			return
		}
	}
	d.Sections = append(d.Sections, section)
}

func (d *Document) Serialize() string {
	var b strings.Builder
	for _, line := range d.Preamble {
		b.WriteString(line)
		b.WriteString("\n")
	}

	if len(d.Preamble) > 0 && len(d.Sections) > 0 {
		b.WriteString("\n")
	}

	for i, section := range d.Sections {
		b.WriteString(fmt.Sprintf("[%s]\n", section.Name))
		for _, line := range section.Lines {
			if line.IsKV {
				if line.Disabled {
					b.WriteString(fmt.Sprintf("# %s = %s\n", line.Key, line.Value))
				} else {
					b.WriteString(fmt.Sprintf("    %s = %s\n", line.Key, line.Value))
				}
				continue
			}

			b.WriteString(line.Raw)
			b.WriteString("\n")
		}

		if i < len(d.Sections)-1 {
			b.WriteString("\n")
		}
	}

	return b.String()
}

type ShareConfig struct {
	Name       string
	Path       string
	Comment    string
	Browseable string
	ReadOnly   string
	GuestOK    string
	ValidUsers []string
}

func ShareFromSection(section *Section) ShareConfig {
	cfg := ShareConfig{
		Name:       section.Name,
		Browseable: "yes",
		ReadOnly:   "no",
		GuestOK:    "no",
	}

	for _, line := range section.Lines {
		if !line.IsKV || line.Disabled {
			continue
		}

		switch line.Key {
		case "path":
			cfg.Path = line.Value
		case "comment":
			cfg.Comment = line.Value
		case "browseable", "browsable":
			cfg.Browseable = line.Value
		case "read only", "readonly":
			cfg.ReadOnly = line.Value
		case "guest ok":
			cfg.GuestOK = line.Value
		case "valid users":
			cfg.ValidUsers = splitUsers(line.Value)
		}
	}

	return cfg
}

func BuildShareSection(existing *Section, cfg ShareConfig) *Section {
	var lines []Line
	if existing != nil {
		lines = slices.Clone(existing.Lines)
	} else {
		lines = []Line{}
	}

	section := &Section{
		Name:  cfg.Name,
		Lines: lines,
	}

	section.SetValue("path", cfg.Path)
	if cfg.Comment != "" {
		section.SetValue("comment", cfg.Comment)
	} else {
		section.DeleteValue("comment")
	}
	section.SetValue("browseable", normalizeBoolish(cfg.Browseable, "yes"))
	section.SetValue("read only", normalizeBoolish(cfg.ReadOnly, "no"))
	section.SetValue("guest ok", normalizeBoolish(cfg.GuestOK, "no"))
	if len(cfg.ValidUsers) > 0 {
		section.SetValue("valid users", strings.Join(cfg.ValidUsers, ", "))
	} else {
		section.DeleteValue("valid users")
	}

	return section
}

func (s *Section) SetValue(key, value string) {
	canonical := strings.ToLower(strings.TrimSpace(key))
	for i, line := range s.Lines {
		if line.IsKV && line.Key == canonical {
			s.Lines[i].Value = value
			s.Lines[i].Disabled = false
			return
		}
	}

	s.Lines = append(s.Lines, Line{
		Key:   canonical,
		Value: value,
		IsKV:  true,
	})
}

func (s *Section) DeleteValue(key string) {
	canonical := strings.ToLower(strings.TrimSpace(key))
	dst := s.Lines[:0]
	for _, line := range s.Lines {
		if line.IsKV && line.Key == canonical {
			continue
		}
		dst = append(dst, line)
	}
	s.Lines = dst
}

func splitUsers(raw string) []string {
	fields := strings.FieldsFunc(raw, func(r rune) bool {
		return r == ',' || r == ' ' || r == '\t'
	})

	var users []string
	for _, field := range fields {
		field = strings.TrimSpace(field)
		if field != "" {
			users = append(users, field)
		}
	}
	return users
}

func normalizeBoolish(value, fallback string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "yes", "true", "1":
		return "yes"
	case "no", "false", "0":
		return "no"
	case "":
		return fallback
	default:
		return value
	}
}
