package tui

import (
	"fmt"
	"strings"
)

type ErrorCategory string

const (
	ErrCatDNS     ErrorCategory = "DNS"
	ErrCatTCP     ErrorCategory = "TCP"
	ErrCatTLS     ErrorCategory = "TLS"
	ErrCatHTTP    ErrorCategory = "HTTP"
	ErrCatICMP    ErrorCategory = "ICMP"
	ErrCatTimeout ErrorCategory = "TMO"
	ErrCatPrivate ErrorCategory = "PRIV"
	ErrCatUnknown ErrorCategory = ""
)

func classifyError(errorReason string, siteType string, statusCode int) ErrorCategory {
	if errorReason == "" {
		return ErrCatUnknown
	}
	lower := strings.ToLower(errorReason)

	if strings.Contains(lower, "resolves to private") || strings.Contains(lower, "private address") || strings.Contains(lower, "private ip") {
		return ErrCatPrivate
	}

	if strings.Contains(lower, "tls handshake") || strings.Contains(lower, "x509") ||
		strings.Contains(lower, "certificate") || strings.Contains(lower, "ssl") {
		return ErrCatTLS
	}

	if strings.Contains(lower, "no such host") || strings.Contains(lower, "nxdomain") ||
		strings.Contains(lower, "dns query") || strings.Contains(lower, "dns rcode") ||
		strings.Contains(lower, "servfail") || strings.Contains(lower, "server misbehaving") {
		return ErrCatDNS
	}

	if strings.Contains(lower, "i/o timeout") || strings.Contains(lower, "deadline exceeded") ||
		strings.Contains(lower, "context canceled") {
		return ErrCatTimeout
	}

	if strings.Contains(lower, "no icmp response") || strings.Contains(lower, "ping setup") ||
		strings.Contains(lower, "ping failed") {
		return ErrCatICMP
	}

	if strings.Contains(lower, "connection refused") || strings.Contains(lower, "connection reset") ||
		strings.Contains(lower, "no route to host") || strings.Contains(lower, "network unreachable") ||
		strings.Contains(lower, "network is unreachable") || strings.Contains(lower, "failed to connect") {
		return ErrCatTCP
	}

	if strings.HasPrefix(lower, "http ") || strings.Contains(lower, "keyword not found") {
		return ErrCatHTTP
	}
	if statusCode >= httpErrorThreshold {
		return ErrCatHTTP
	}

	return ErrCatUnknown
}

func categoryTag(cat ErrorCategory) string {
	if cat == ErrCatUnknown {
		return ""
	}
	return "[" + string(cat) + "]"
}

type stepStatus int

const (
	stepPassed stepStatus = iota
	stepFailed
	stepSkipped
)

type chainStep struct {
	Name   string
	Status stepStatus
	Detail string
}

func connectionChain(errorReason string, siteType string, statusCode int, isHTTPS bool) []chainStep {
	if siteType != "http" {
		return nil
	}
	if errorReason == "" {
		return nil
	}

	cat := classifyError(errorReason, siteType, statusCode)
	detail := extractDetail(errorReason, cat, statusCode)

	steps := []struct {
		name string
		cat  ErrorCategory
	}{
		{"DNS resolve", ErrCatDNS},
		{"TCP connect", ErrCatTCP},
	}
	if isHTTPS {
		steps = append(steps, struct {
			name string
			cat  ErrorCategory
		}{"TLS handshake", ErrCatTLS})
	}
	steps = append(steps, struct {
		name string
		cat  ErrorCategory
	}{"HTTP response", ErrCatHTTP})

	chain := make([]chainStep, 0, len(steps))
	failed := false
	for _, s := range steps {
		switch {
		case failed:
			chain = append(chain, chainStep{Name: s.name, Status: stepSkipped, Detail: "(skipped)"})
		case s.cat == cat || (cat == ErrCatTimeout && s.cat == ErrCatTCP) || (cat == ErrCatPrivate && s.cat == ErrCatDNS):
			chain = append(chain, chainStep{Name: s.name, Status: stepFailed, Detail: detail})
			failed = true
		default:
			chain = append(chain, chainStep{Name: s.name, Status: stepPassed})
		}
	}

	return chain
}

func extractDetail(errorReason string, cat ErrorCategory, statusCode int) string {
	lower := strings.ToLower(errorReason)
	var detail string

	switch cat {
	case ErrCatDNS:
		for _, keyword := range []string{"NXDOMAIN", "SERVFAIL", "REFUSED", "no such host"} {
			if strings.Contains(strings.ToUpper(errorReason), strings.ToUpper(keyword)) {
				detail = keyword
				break
			}
		}
		if detail == "" {
			detail = limitStr(errorReason, errorDetailMaxLen)
		}
	case ErrCatTCP:
		for _, keyword := range []string{"connection refused", "connection reset", "no route to host", "network unreachable"} {
			if strings.Contains(lower, keyword) {
				detail = keyword
				break
			}
		}
		if detail == "" {
			detail = limitStr(errorReason, errorDetailMaxLen)
		}
	case ErrCatTLS:
		for _, keyword := range []string{"certificate expired", "certificate has expired", "handshake failure", "unknown authority"} {
			if strings.Contains(lower, keyword) {
				detail = keyword
				break
			}
		}
		if detail == "" && strings.Contains(lower, "x509:") {
			idx := strings.Index(lower, "x509:")
			detail = limitStr(errorReason[idx:], errorDetailMaxLen)
		}
		if detail == "" {
			detail = limitStr(errorReason, errorDetailMaxLen)
		}
	case ErrCatHTTP:
		if statusCode > 0 {
			detail = fmt.Sprintf("HTTP %d", statusCode)
		} else {
			detail = limitStr(errorReason, errorDetailMaxLen)
		}
	case ErrCatTimeout:
		if strings.Contains(lower, "i/o timeout") {
			detail = "i/o timeout"
		} else {
			detail = "deadline exceeded"
		}
	case ErrCatICMP:
		if strings.Contains(lower, "no icmp response") {
			detail = "no response"
		} else {
			detail = limitStr(errorReason, errorDetailMaxLen)
		}
	case ErrCatPrivate:
		detail = "private IP blocked"
	default:
		detail = limitStr(errorReason, errorDetailMaxLen)
	}

	return detail
}
