package contract

import (
	"bufio"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"testing"
)

var httpMethods = map[string]bool{
	"get": true, "post": true, "put": true, "patch": true, "delete": true,
}

func TestOpenAPIOperationsAreRegisteredInRouter(t *testing.T) {
	operations := parseOpenAPIOperations(t)
	routes := parseRouterRoutes(t)
	if len(operations) == 0 {
		t.Fatal("expected OpenAPI operations")
	}
	if len(routes) == 0 {
		t.Fatal("expected router routes")
	}

	var missing []string
	for operation := range operations {
		if !routes[operation] {
			missing = append(missing, operation)
		}
	}
	if len(missing) > 0 {
		t.Fatalf("OpenAPI operations missing in router: %s", strings.Join(missing, ", "))
	}
}

func TestOpenAPIOperationsDeclareResponses(t *testing.T) {
	openAPIPath := repoPath(t, "backend", "docs", "openapi.yaml")
	file, err := os.Open(openAPIPath)
	if err != nil {
		t.Fatalf("open OpenAPI spec: %v", err)
	}
	defer file.Close()

	currentPath := ""
	currentOperation := ""
	hasResponses := map[string]bool{}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		trimmed := strings.TrimSpace(line)
		if strings.HasPrefix(line, "  /") && strings.HasSuffix(trimmed, ":") {
			currentPath = strings.TrimSuffix(trimmed, ":")
			currentOperation = ""
			continue
		}
		if currentPath != "" && strings.HasPrefix(line, "    ") && !strings.HasPrefix(line, "      ") && strings.HasSuffix(trimmed, ":") {
			method := strings.TrimSuffix(trimmed, ":")
			if httpMethods[method] {
				currentOperation = strings.ToUpper(method) + " " + currentPath
				hasResponses[currentOperation] = false
			}
			continue
		}
		if currentOperation != "" && strings.HasPrefix(line, "      responses:") {
			hasResponses[currentOperation] = true
		}
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("scan OpenAPI spec: %v", err)
	}

	var missing []string
	for operation, ok := range hasResponses {
		if !ok {
			missing = append(missing, operation)
		}
	}
	if len(missing) > 0 {
		t.Fatalf("OpenAPI operations without responses: %s", strings.Join(missing, ", "))
	}
}

func parseOpenAPIOperations(t *testing.T) map[string]bool {
	t.Helper()
	openAPIPath := repoPath(t, "backend", "docs", "openapi.yaml")
	file, err := os.Open(openAPIPath)
	if err != nil {
		t.Fatalf("open OpenAPI spec: %v", err)
	}
	defer file.Close()

	operations := map[string]bool{}
	currentPath := ""
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		trimmed := strings.TrimSpace(line)
		if strings.HasPrefix(line, "  /") && strings.HasSuffix(trimmed, ":") {
			currentPath = strings.TrimSuffix(trimmed, ":")
			continue
		}
		if currentPath != "" && strings.HasPrefix(line, "    ") && !strings.HasPrefix(line, "      ") && strings.HasSuffix(trimmed, ":") {
			method := strings.TrimSuffix(trimmed, ":")
			if httpMethods[method] {
				operations[strings.ToUpper(method)+" "+currentPath] = true
			}
		}
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("scan OpenAPI spec: %v", err)
	}
	return operations
}

func parseRouterRoutes(t *testing.T) map[string]bool {
	t.Helper()
	routerPath := repoPath(t, "backend", "internal", "httpapi", "router.go")
	data, err := os.ReadFile(routerPath)
	if err != nil {
		t.Fatalf("read router.go: %v", err)
	}
	routes := map[string]bool{}
	pattern := regexp.MustCompile(`mux\.Handle(?:Func)?\("([A-Z]+) ([^"]+)"`)
	for _, match := range pattern.FindAllStringSubmatch(string(data), -1) {
		routes[match[1]+" "+match[2]] = true
	}
	return routes
}

func repoPath(t *testing.T, parts ...string) string {
	t.Helper()
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("resolve test path")
	}
	root := filepath.Clean(filepath.Join(filepath.Dir(file), "..", ".."))
	all := append([]string{root}, parts...)
	return filepath.Join(all...)
}
