package consenttool

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

func Run(configPath string, mode string) error {
	cfg, err := LoadConfig(configPath)
	if err != nil {
		return err
	}
	baseDir := filepath.Dir(configPath)

	switch mode {
	case "generate-json":
		return generateJSON(cfg, baseDir)
	case "extract-log":
		return extractLog(cfg, baseDir)
	default:
		return fmt.Errorf("unknown mode %q", mode)
	}
}

func generateJSON(cfg *Config, baseDir string) error {
	inputPath := resolvePath(baseDir, cfg.Consent.InputCSV)
	outputPath := resolvePath(baseDir, cfg.Consent.OutputJSON)

	rows, err := readCSVRows(inputPath)
	if err != nil {
		return err
	}

	names := collectConsentedNames(rows, cfg.Consent)
	body, err := json.MarshalIndent(names, "", "  ")
	if err != nil {
		return err
	}

	if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
		return err
	}
	return os.WriteFile(outputPath, append(body, '\n'), 0o644)
}

func extractLog(cfg *Config, baseDir string) error {
	inputPath := resolvePath(baseDir, cfg.Log.InputPath)
	outputPath := resolvePath(baseDir, cfg.Log.OutputPath)

	data, err := os.ReadFile(inputPath)
	if err != nil {
		return err
	}

	re, err := regexp.Compile(cfg.Log.IncludeRegex)
	if err != nil {
		return err
	}

	var kept []string
	for _, line := range strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") {
		if re.MatchString(line) {
			kept = append(kept, line)
			continue
		}
		if cfg.Log.KeepNonMatch {
			kept = append(kept, line)
		}
	}

	body := strings.Join(kept, "\n")
	if body != "" {
		body += "\n"
	}
	if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
		return err
	}
	return os.WriteFile(outputPath, []byte(body), 0o644)
}

func readCSVRows(path string) ([]map[string]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	r := csv.NewReader(file)
	headers, err := r.Read()
	if err != nil {
		return nil, err
	}
	for i, header := range headers {
		headers[i] = strings.TrimPrefix(strings.TrimSpace(header), "\ufeff")
	}

	var rows []map[string]string
	for {
		record, err := r.Read()
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		row := make(map[string]string, len(headers))
		for i, header := range headers {
			if i < len(record) {
				row[strings.TrimSpace(header)] = strings.TrimPrefix(strings.TrimSpace(record[i]), "\ufeff")
			}
		}
		rows = append(rows, row)
	}
	return rows, nil
}

func collectConsentedNames(rows []map[string]string, cfg ConsentConfig) []string {
	displayFields := cfg.DisplayFields
	if len(displayFields) == 0 {
		displayFields = []string{"display_name", "vrchat_name", "name"}
	}
	consentFields := cfg.ConsentFields
	if len(consentFields) == 0 {
		consentFields = []string{"consent", "agree", "agreed", "checked"}
	}
	accepted := make(map[string]struct{})
	var names []string

	for _, row := range rows {
		if !rowIsConsented(row, consentFields, cfg.ConsentValues) {
			continue
		}
		name := firstNonEmpty(row, displayFields)
		if name == "" {
			continue
		}
		key := name
		if cfg.NormalizeCase {
			key = strings.ToLower(key)
		}
		if !cfg.AllowDuplicates {
			if _, ok := accepted[key]; ok {
				continue
			}
			accepted[key] = struct{}{}
		}
		names = append(names, name)
	}
	return names
}

func rowIsConsented(row map[string]string, fields []string, values []string) bool {
	if len(values) == 0 {
		values = []string{"1", "true", "yes", "y", "on", "checked"}
	}
	allowed := make(map[string]struct{}, len(values))
	for _, v := range values {
		allowed[strings.ToLower(strings.TrimSpace(v))] = struct{}{}
	}
	for _, field := range fields {
		if v, ok := row[field]; ok {
			if _, ok := allowed[strings.ToLower(strings.TrimSpace(v))]; ok {
				return true
			}
		}
	}
	return false
}

func firstNonEmpty(row map[string]string, fields []string) string {
	for _, field := range fields {
		if v := strings.TrimSpace(row[field]); v != "" {
			return v
		}
	}
	return ""
}
