package staticsite

import (
	"context"
	"errors"
	"net"
	"net/http"
	"strings"
	"testing"
	"time"
)

func TestValidateBaseURL(t *testing.T) {
	cases := []struct {
		name      string
		input     string
		wantError bool
	}{
		{"https", "https://git.example.com", false},
		{"http", "http://git.example.com", false},
		{"trailing_slash", "https://git.example.com/", false},
		{"with_path", "https://git.example.com/sub", false},
		{"with_port", "https://git.example.com:8080", false},
		{"empty", "", true},
		{"whitespace_only", "   ", true},
		{"ftp_scheme", "ftp://git.example.com", true},
		{"file_scheme", "file:///etc/passwd", true},
		{"no_scheme", "git.example.com", true},
		{"scheme_no_host", "https://", true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidateBaseURL(tc.input)
			if tc.wantError && err == nil {
				t.Errorf("ValidateBaseURL(%q) = nil, want error", tc.input)
			}
			if !tc.wantError && err != nil {
				t.Errorf("ValidateBaseURL(%q) = %v, want nil", tc.input, err)
			}
		})
	}
}

func TestBlockReason_PolicyMatrix(t *testing.T) {
	cases := []struct {
		name        string
		ip          string
		wantBlocked bool
	}{
		// Allowed.
		{"public_v4", "8.8.8.8", false},
		{"rfc1918_10", "10.0.0.1", false},
		{"rfc1918_172_16", "172.16.0.1", false},
		{"rfc1918_192_168", "192.168.1.1", false},
		{"public_v6", "2606:4700:4700::1111", false},
		{"ula_v6", "fd00::1", false}, // ULA private — allowed, mirrors RFC1918

		// Blocked.
		{"loopback_v4", "127.0.0.1", true},
		{"loopback_v6", "::1", true},
		{"unspecified_v4", "0.0.0.0", true},
		{"unspecified_v6", "::", true},
		{"link_local_v4_metadata", "169.254.169.254", true}, // AWS/GCP metadata
		{"link_local_v6", "fe80::1", true},
		{"multicast_v4", "224.0.0.1", true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ip := net.ParseIP(tc.ip)
			if ip == nil {
				t.Fatalf("parse %q", tc.ip)
			}
			got := blockReason(ip)
			blocked := got != ""
			if blocked != tc.wantBlocked {
				t.Errorf("blockReason(%s) = %q (blocked=%v), want blocked=%v",
					tc.ip, got, blocked, tc.wantBlocked)
			}
		})
	}
}

// TestSafeHTTPClient_RejectsLoopbackLiteral exercises the actual dial
// path: a request to a loopback literal must fail before any TCP work
// happens, with ErrBlockedAddress in the chain.
func TestSafeHTTPClient_RejectsLoopbackLiteral(t *testing.T) {
	client := NewSafeHTTPClient(2 * time.Second)
	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:1/", nil)
	if err != nil {
		t.Fatalf("new request: %v", err)
	}
	_, err = client.Do(req)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !errors.Is(err, ErrBlockedAddress) && !strings.Contains(err.Error(), "blocked") {
		t.Errorf("err = %v, expected ErrBlockedAddress in chain or 'blocked' in message", err)
	}
}

// TestSafeHTTPClient_RejectsAWSMetadataLiteral mirrors the loopback
// case but for the AWS/GCP cloud metadata IP (link-local).
func TestSafeHTTPClient_RejectsAWSMetadataLiteral(t *testing.T) {
	client := NewSafeHTTPClient(2 * time.Second)
	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://169.254.169.254/latest/meta-data/", nil)
	if err != nil {
		t.Fatalf("new request: %v", err)
	}
	_, err = client.Do(req)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !errors.Is(err, ErrBlockedAddress) && !strings.Contains(err.Error(), "blocked") {
		t.Errorf("err = %v, expected ErrBlockedAddress in chain or 'blocked' in message", err)
	}
}
