package hostfile

import (
	"fmt"
	"strings"
)

// ParseManagedBlock splits raw file lines into prefix, managed, and postfix sections.
// Returns an error if the managed block markers are corrupt.
//
// Uses -1 as sentinel for "not found" because the start marker can appear at line 0.
// The current code's zero-index bug (treating index 0 as "not found") is avoided here.
func ParseManagedBlock(lines []string) (prefix, managed, postfix []string, hasManagedBlock bool, err error) {
	startIdx := -1 // sentinel: not found
	endIdx := -1   // sentinel: not found
	startCount := 0
	endCount := 0

	for i, line := range lines {
		// Recognise both current and legacy markers so that hosts files
		// written by the old "DNSHelper" binary are parsed correctly.
		if strings.Contains(line, "ekDNSHelper <<-> START CONFIG") ||
			strings.Contains(line, "DNSHelper <<-> START CONFIG") {
			startCount++
			if startIdx == -1 {
				startIdx = i
			}
		}
		if strings.Contains(line, "ekDNSHelper <->> END CONFIG") ||
			strings.Contains(line, "DNSHelper <->> END CONFIG") {
			endCount++
			if endIdx == -1 {
				endIdx = i
			}
		}
	}

	// Corruption detection (R-008): scan ALL lines first, then evaluate.
	if startCount > 1 || endCount > 1 {
		return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: duplicate start/end markers found")
	}
	if startIdx >= 0 && endIdx < 0 {
		return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: start marker found at line %d but no end marker", startIdx+1)
	}
	if endIdx >= 0 && startIdx < 0 {
		return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: end marker found at line %d but no start marker", endIdx+1)
	}
	if startIdx >= 0 && endIdx >= 0 && endIdx < startIdx {
		return nil, nil, nil, false, fmt.Errorf("managed block is corrupt: end marker (line %d) appears before start marker (line %d)", endIdx+1, startIdx+1)
	}

	if startIdx < 0 && endIdx < 0 {
		// No managed block — all lines are prefix.
		return lines, nil, nil, false, nil
	}

	// Valid managed block.
	prefix = lines[:startIdx]
	managed = lines[startIdx+1 : endIdx]
	postfix = lines[endIdx+1:]
	return prefix, managed, postfix, true, nil
}

// lineMatchesHostname parses a hosts-file line ("IP<whitespace>hostname [aliases...]")
// and returns true if any hostname field (including aliases) matches,
// using case-insensitive comparison per RFC 4343.
func lineMatchesHostname(line, hostname string) bool {
	fields := strings.Fields(line)
	for _, f := range fields[1:] {
		if strings.EqualFold(f, hostname) {
			return true
		}
	}
	return false
}

// AddEntries removes existing entries for the same hostnames as the new entries,
// then appends the new entries. Returns deduplicated managed content lines.
func AddEntries(existing []string, entries []DNSEntry) []string {
	// Collect hostnames being added.
	hostnames := make(map[string]bool)
	for _, e := range entries {
		hostnames[e.Hostname] = true
	}

	// Keep existing lines that don't match any new hostname.
	var result []string
	for _, line := range existing {
		keep := true
		for h := range hostnames {
			if lineMatchesHostname(line, h) {
				keep = false
				break
			}
		}
		if keep {
			result = append(result, line)
		}
	}

	// Append new entries.
	for _, e := range entries {
		result = append(result, e.String())
	}

	// Deduplicate preserving order.
	return deduplicateLines(result)
}

// RemoveByHostname removes all managed entries matching any of the specified
// hostnames. Returns the remaining content.
//
// The correct algorithm iterates lines on the outer loop (not hosts), which
// avoids the duplication bug in the legacy removeHostsFromExistingContent
// function (dataprep.go).
func RemoveByHostname(existing []string, hostnames []string) []string {
	var result []string
	for _, line := range existing {
		shouldRemove := false
		for _, host := range hostnames {
			if lineMatchesHostname(line, host) {
				shouldRemove = true
				break
			}
		}
		if !shouldRemove {
			result = append(result, line)
		}
	}
	return result
}

// RemoveAll returns nil, indicating all managed entries should be removed.
// When AssembleContent receives nil/empty managed content, it omits the
// managed block entirely (markers included).
func RemoveAll() []string {
	return nil
}

// AssembleContent builds the full file content from sections.
// If managed is empty, the managed block (markers included) is omitted entirely.
func AssembleContent(prefix, managed, postfix []string) []string {
	var result []string
	result = append(result, prefix...)
	if len(managed) > 0 {
		result = append(result, StartMarker)
		result = append(result, managed...)
		result = append(result, EndMarker)
	}
	result = append(result, postfix...)
	return result
}

// deduplicateLines removes duplicate lines preserving order.
func deduplicateLines(lines []string) []string {
	seen := make(map[string]bool)
	var result []string
	for _, line := range lines {
		if !seen[line] {
			seen[line] = true
			result = append(result, line)
		}
	}
	return result
}
