package http

import (
	"crypto/tls"
	"fmt"
	"net"
	nethttp "net/http"
	"time"

	"arcline-audit/internal/types"
)

const maxRedirects = 20

// securityHeaders maps header names to labels for reporting.
var securityHeaders = map[string]string{
	"Strict-Transport-Security": "HSTS",
	"Content-Security-Policy":   "CSP",
	"X-Frame-Options":           "X-Frame-Options",
	"X-Content-Type-Options":    "X-Content-Type-Options",
	"Referrer-Policy":           "Referrer-Policy",
}

// Run performs all HTTP checks for the given domain.
func Run(domain string) types.HTTPResult {
	client := &nethttp.Client{
		Timeout: 15 * time.Second,
		CheckRedirect: func(req *nethttp.Request, via []*nethttp.Request) error {
			if len(via) >= maxRedirects {
				return fmt.Errorf("too many redirects")
			}
			return nil
		},
		Transport: &nethttp.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
			DialContext:     (&net.Dialer{Timeout: 10 * time.Second}).DialContext,
		},
	}

	// Start from HTTP to detect http→https redirect
	startURL := "http://" + domain
	var checks []types.CheckResult

	start := time.Now()
	resp, redirectHops, finalURL, redirectChecks := followRedirects(client, startURL)
	elapsed := time.Since(start)

	checks = append(checks, redirectChecks...)

	result := types.HTTPResult{
		Checks:       checks,
		ResponseTime: elapsed,
		FinalURL:     finalURL,
		RedirectHops: redirectHops,
	}

	if resp == nil {
		result.Checks = result.Checks[:len(result.Checks):len(result.Checks)]
		return result
	}
	defer resp.Body.Close()

	// Check security headers
	result.Checks = append(result.Checks, checkSecurityHeaders(resp.Header)...)

	// Check server header disclosure
	result.Checks = append(result.Checks, checkServerHeader(resp.Header)...)

	// Response time
	result.Checks = append(result.Checks, types.CheckResult{
		Status:  types.StatusOK,
		Message: fmt.Sprintf("response time %dms", elapsed.Milliseconds()),
	})

	return result
}

func followRedirects(client *nethttp.Client, startURL string) (*nethttp.Response, int, string, []types.CheckResult) {
	var checks []types.CheckResult
	visited := make(map[string]bool)
	hops := 0
	current := startURL

	for hops <= maxRedirects {
		visited[current] = true

		req, err := nethttp.NewRequest("GET", current, nil)
		if err != nil {
			checks = append(checks, types.CheckResult{
				Status: types.StatusFail, Message: fmt.Sprintf("request error: %v", err),
			})
			return nil, hops, current, checks
		}

		resp, err := client.Do(req)
		if err != nil {
			checks = append(checks, types.CheckResult{
				Status: types.StatusFail, Message: fmt.Sprintf("connection error: %v", err),
			})
			return nil, hops, current, checks
		}

		// If non-redirect response
		if resp.StatusCode < 300 || resp.StatusCode >= 400 {
			if hops > 0 {
				checks = append(checks, types.CheckResult{
					Status:  types.StatusOK,
					Message: fmt.Sprintf("redirects http → https (%d hop(s))", hops),
				})
			}
			return resp, hops, current, checks
		}

		// Follow redirect
		hops++
		loc, err := resp.Location()
		resp.Body.Close()
		if err != nil {
			checks = append(checks, types.CheckResult{
				Status:  types.StatusOK,
				Message: fmt.Sprintf("redirects followed: %d hop(s)", hops),
			})
			return resp, hops, current, checks
		}
		nextURL := loc.String()

		// Detect redirect loop
		if visited[nextURL] {
			checks = append(checks, types.CheckResult{
				Status:  types.StatusFail,
				Message: "redirect loop detected",
			})
			return resp, hops, nextURL, checks
		}

		current = nextURL
	}

	checks = append(checks, types.CheckResult{
		Status:  types.StatusFail,
		Message: fmt.Sprintf("too many redirects (%d)", hops),
	})
	return nil, hops, current, checks
}

func checkSecurityHeaders(headers nethttp.Header) []types.CheckResult {
	var checks []types.CheckResult

	for header, label := range securityHeaders {
		val := headers.Get(header)
		if val != "" {
			checks = append(checks, types.CheckResult{
				Status:  types.StatusOK,
				Message: fmt.Sprintf("%s header present", label),
			})
		} else {
			switch header {
			case "Strict-Transport-Security":
				checks = append(checks, types.CheckResult{
					Status: types.StatusWarn, Message: "no HSTS header",
				})
			case "Content-Security-Policy":
				checks = append(checks, types.CheckResult{
					Status: types.StatusInfo, Message: "no CSP header",
				})
			case "X-Frame-Options":
				checks = append(checks, types.CheckResult{
					Status: types.StatusInfo, Message: "no X-Frame-Options header",
				})
			default:
				checks = append(checks, types.CheckResult{
					Status: types.StatusInfo, Message: fmt.Sprintf("no %s header", label),
				})
			}
		}
	}

	return checks
}

func checkServerHeader(headers nethttp.Header) []types.CheckResult {
	server := headers.Get("Server")
	if server == "" {
		return []types.CheckResult{{Status: types.StatusOK, Message: "no Server header disclosed"}}
	}
	// Trim long server strings
	if len(server) > 60 {
		server = server[:57] + "..."
	}
	return []types.CheckResult{{Status: types.StatusWarn, Message: fmt.Sprintf("Server header disclosed: %s", server)}}
}

