package catalogpublicationv2store

import (
	"context"
	"database/sql"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"testing"

	catalogpublicationv2 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalogpublicationv2"
)

var (
	_ func(context.Context, *sql.DB) (*VerifiedTable, error)                                      = CreateHaltedShadow
	_ func(context.Context, *sql.DB) (*VerifiedTable, error)                                      = VerifyShadow
	_ func(context.Context, *sql.DB) (*VerifiedTable, error)                                      = VerifyLive
	_ func(context.Context, *VerifiedTable, catalogpublicationv2.Binding) (string, error)         = PutExact
	_ func(context.Context, *VerifiedTable, string, string) (catalogpublicationv2.Binding, error) = ReadExact
)

func TestPackageExportsOnlyFixedVerificationAndImmutableStorePrimitives(t *testing.T) {
	want := []string{
		"func:CreateHaltedShadow",
		"func:PutExact",
		"func:ReadExact",
		"func:VerifyLive",
		"func:VerifyShadow",
		"type:VerifiedTable",
		"var:ErrCorrupt",
		"var:ErrNotFound",
		"var:ErrSchema",
	}
	got := exportedDeclarations(t)
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("exported package surface = %v, want %v", got, want)
	}
}

func TestPackageImportsOnlyV2ContractAndStandardLibrary(t *testing.T) {
	const allowedContract = "github.com/pingcap-inc/nexuscrm-contract/pkg/catalogpublicationv2"
	for _, path := range productionFiles(t) {
		parsed, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly)
		if err != nil {
			t.Fatal(err)
		}
		for _, spec := range parsed.Imports {
			importPath, err := strconv.Unquote(spec.Path.Value)
			if err != nil {
				t.Fatal(err)
			}
			first := strings.Split(importPath, "/")[0]
			if !strings.Contains(first, ".") {
				continue
			}
			if importPath != allowedContract {
				t.Errorf("strict v2 store imports non-v2 dependency %s", importPath)
			}
		}
	}
}

func TestStrictV2StoreHasNoProductionConsumerBeforeHaltedCutover(t *testing.T) {
	repoRoot := packageRepoRoot(t)
	packageDir := filepath.Join(repoRoot, "internal", "catalogpublicationv2store")
	const importPath = "github.com/pingcap-inc/nexuscrm-controlplane/internal/catalogpublicationv2store"
	var consumers []string
	err := filepath.WalkDir(repoRoot, func(path string, entry os.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if entry.IsDir() {
			if entry.Name() == ".git" || entry.Name() == "vendor" {
				return filepath.SkipDir
			}
			return nil
		}
		if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") || strings.HasPrefix(path, packageDir+string(filepath.Separator)) {
			return nil
		}
		parsed, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly)
		if err != nil {
			return err
		}
		for _, spec := range parsed.Imports {
			value, err := strconv.Unquote(spec.Path.Value)
			if err != nil {
				return err
			}
			if value == importPath {
				relative, err := filepath.Rel(repoRoot, path)
				if err != nil {
					return err
				}
				consumers = append(consumers, filepath.ToSlash(relative))
			}
		}
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(consumers) != 0 {
		t.Fatalf("strict v2 store production consumers before halted cutover = %v", consumers)
	}
}

func exportedDeclarations(t *testing.T) []string {
	t.Helper()
	var result []string
	for _, path := range productionFiles(t) {
		parsed, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
		if err != nil {
			t.Fatal(err)
		}
		for _, declaration := range parsed.Decls {
			switch declaration := declaration.(type) {
			case *ast.FuncDecl:
				if ast.IsExported(declaration.Name.Name) {
					result = append(result, "func:"+declaration.Name.Name)
				}
			case *ast.GenDecl:
				for _, spec := range declaration.Specs {
					switch spec := spec.(type) {
					case *ast.TypeSpec:
						if ast.IsExported(spec.Name.Name) {
							result = append(result, "type:"+spec.Name.Name)
						}
					case *ast.ValueSpec:
						for _, name := range spec.Names {
							if ast.IsExported(name.Name) {
								result = append(result, strings.ToLower(declaration.Tok.String())+":"+name.Name)
							}
						}
					}
				}
			}
		}
	}
	sort.Strings(result)
	return result
}

func productionFiles(t *testing.T) []string {
	t.Helper()
	packageDir := filepath.Join(packageRepoRoot(t), "internal", "catalogpublicationv2store")
	entries, err := os.ReadDir(packageDir)
	if err != nil {
		t.Fatal(err)
	}
	var result []string
	for _, entry := range entries {
		if entry.IsDir() || filepath.Ext(entry.Name()) != ".go" || strings.HasSuffix(entry.Name(), "_test.go") {
			continue
		}
		result = append(result, filepath.Join(packageDir, entry.Name()))
	}
	sort.Strings(result)
	return result
}

func packageRepoRoot(t *testing.T) string {
	t.Helper()
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("resolve package path")
	}
	return filepath.Dir(filepath.Dir(filepath.Dir(file)))
}
