package main

import (
	"regexp"
	"strings"
)

var (
	reLevel = regexp.MustCompile(`(?i)\b(INFO|WARN|WARNING|ERROR|DEBUG|TRACE)\b`)
	reHTTP  = regexp.MustCompile(`\b(HTTP/\d\.\d"\s+)?([1-5]\d\d)\b`)
	reMs    = regexp.MustCompile(`(?i)\b(?:duration|latency|took|elapsed)\s*=\s*(\d+)\s*ms\b`)
	reErrKW = regexp.MustCompile(`(?i)\b(timeout|panic|exception|failed|refused|unreachable|denied)\b`)
)

type ParsedLine struct {
	Level     string
	HTTPCode  int
	LatencyMs int
	ErrKW     []string
}

func ParseLine(s string) (ParsedLine, bool) {
	out := ParsedLine{Level: "UNKNOWN"}
	trim := strings.TrimSpace(s)
	if trim == "" {
		return out, false
	}

	// Level
	if m := reLevel.FindStringSubmatch(trim); len(m) >= 2 {
		lvl := strings.ToUpper(m[1])
		if lvl == "WARNING" {
			lvl = "WARN"
		}
		out.Level = lvl
	} else {
		// heuristic: common prefixes
		up := strings.ToUpper(trim)
		switch {
		case strings.Contains(up, " ERR "):
			out.Level = "ERROR"
		case strings.Contains(up, " WRN "):
			out.Level = "WARN"
		case strings.Contains(up, " INF "):
			out.Level = "INFO"
		}
	}

	// HTTP status code (best effort)
	if m := reHTTP.FindStringSubmatch(trim); len(m) >= 3 {
		code := atoiSafe(m[2])
		if code >= 100 && code <= 599 {
			out.HTTPCode = code
		}
	}

	// latency ms (best effort)
	if m := reMs.FindStringSubmatch(trim); len(m) >= 2 {
		out.LatencyMs = atoiSafe(m[1])
	}

	// error keywords
	kws := reErrKW.FindAllStringSubmatch(trim, -1)
	if len(kws) > 0 {
		uniq := map[string]struct{}{}
		for _, k := range kws {
			if len(k) >= 2 {
				uniq[strings.ToLower(k[1])] = struct{}{}
			}
		}
		out.ErrKW = make([]string, 0, len(uniq))
		for k := range uniq {
			out.ErrKW = append(out.ErrKW, k)
		}
	}

	return out, true
}

func atoiSafe(s string) int {
	n := 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c < '0' || c > '9' {
			break
		}
		n = n*10 + int(c-'0')
	}
	return n
}
