//go:build ignore

package main

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"io/fs"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

var forbidden = map[string]map[string]bool{
	"net": {
		"Listen": true, "ListenConfig": true, "ListenIP": true,
		"ListenMulticastUDP": true, "ListenPacket": true, "ListenTCP": true,
		"ListenUDP": true, "ListenUnix": true, "ListenUnixgram": true,
		"FileListener": true, "FilePacketConn": true,
	},
	"crypto/tls": {"Listen": true, "NewListener": true},
	"net/http": {
		"Server": true, "Serve": true, "ServeTLS": true,
		"ListenAndServe": true, "ListenAndServeTLS": true,
	},
}

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintln(os.Stderr, "usage: check-meshx-runtime-topology.go ROOT")
		os.Exit(2)
	}
	root := filepath.Clean(os.Args[1])
	var failures []string
	for _, subtree := range []string{filepath.Join("internal", "resource", "server")} {
		err := filepath.WalkDir(filepath.Join(root, subtree), func(path string, entry fs.DirEntry, err error) error {
			if err != nil {
				return err
			}
			if entry.IsDir() && entry.Name() == "testdata" {
				return filepath.SkipDir
			}
			if entry.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
				return nil
			}
			found, err := scan(root, path)
			failures = append(failures, found...)
			return err
		})
		if err != nil && !os.IsNotExist(err) {
			fmt.Fprintf(os.Stderr, "scan %s: %v\n", subtree, err)
			os.Exit(2)
		}
	}
	if len(failures) == 0 {
		return
	}
	fmt.Fprintln(os.Stderr, "MeshX unified-runtime topology guardrail failed:")
	for _, item := range failures {
		fmt.Fprintln(os.Stderr, "- "+item)
	}
	os.Exit(1)
}

func scan(root, path string) ([]string, error) {
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, path, nil, 0)
	if err != nil {
		return nil, err
	}
	relative, _ := filepath.Rel(root, path)
	aliases := map[string]string{}
	var failures []string
	fail := func(pos token.Pos, message string) {
		failures = append(failures, fmt.Sprintf(
			"%s:%d: %s; MeshX may expose handlers but must not own listener/server capability",
			filepath.ToSlash(relative), fset.Position(pos).Line, message,
		))
	}

	for _, spec := range file.Imports {
		importPath, err := strconv.Unquote(spec.Path.Value)
		if err != nil {
			return nil, err
		}
		if forbidden[importPath] == nil {
			continue
		}
		alias := filepath.Base(importPath)
		if spec.Name != nil {
			alias = spec.Name.Name
		}
		switch alias {
		case "_":
			continue
		case ".":
			fail(spec.Pos(), "dot import of "+importPath+" is forbidden")
		default:
			aliases[alias] = importPath
		}
	}

	ast.Inspect(file, func(node ast.Node) bool {
		selector, ok := node.(*ast.SelectorExpr)
		if !ok {
			return true
		}
		name, ok := selector.X.(*ast.Ident)
		if ok && name.Obj == nil && forbidden[aliases[name.Name]][selector.Sel.Name] {
			fail(selector.Pos(), aliases[name.Name]+"."+selector.Sel.Name+" is forbidden")
		}
		return true
	})
	return failures, nil
}
