package main

import (
	"fmt"
	"go/format"
	"go/types"
	"sort"
	"strings"
)

type GeneratorOptions struct {
	PackageName   string
	InterfaceName string
	Interface     *types.Interface
	Interfaces    []InterfaceSpec
	PackageInfo   *PackageInfo
}

type Generator struct {
	options GeneratorOptions
}

func NewGenerator(options GeneratorOptions) *Generator {
	return &Generator{options: options}
}

type Method struct {
	Name       string
	Params     []Param
	Results    []Result
	ResultDecl string
}

type Param struct {
	Name string
	Type string
}

type Result struct {
	Name      string
	Type      string
	IsError   bool
	ZeroValue string
}

type Import struct {
	Name string
	Path string
}

type InterfaceSpec struct {
	Name      string
	Interface *types.Interface
}

type InterfaceData struct {
	InterfaceName string
	StructName    string
	Methods       []Method
}

func (g *Generator) Generate() ([]byte, error) {
	interfaces, err := g.resolveInterfaces()
	if err != nil {
		return nil, err
	}

	needsErrorVar := false
	importSet := map[string]Import{}
	for _, spec := range interfaces {
		iface := spec.Interface
		iface.Complete()
		for i := 0; i < iface.NumMethods(); i++ {
			method := iface.Method(i)
			sig, ok := method.Type().(*types.Signature)
			if !ok {
				continue
			}
			collectImportsFromSignature(sig, g.options.PackageInfo.Package, importSet)
			for r := 0; r < sig.Results().Len(); r++ {
				if isErrorType(sig.Results().At(r).Type()) {
					needsErrorVar = true
				}
			}
		}
	}

	aliasMap := buildAliasMap(importSet)
	qualifier := func(pkg *types.Package) string {
		if pkg == nil || pkg.Path() == g.options.PackageInfo.Package.Path() {
			return ""
		}
		if alias, ok := aliasMap[pkg.Path()]; ok {
			return alias
		}
		return pkg.Name()
	}

	interfaceData := make([]InterfaceData, 0, len(interfaces))
	for _, spec := range interfaces {
		iface := spec.Interface
		iface.Complete()
		methods := make([]Method, 0, iface.NumMethods())
		for i := 0; i < iface.NumMethods(); i++ {
			method := iface.Method(i)
			sig, ok := method.Type().(*types.Signature)
			if !ok {
				continue
			}

			params := buildParams(sig, qualifier)
			results := buildResults(sig, qualifier)

			methods = append(methods, Method{
				Name:       method.Name(),
				Params:     params,
				Results:    results,
				ResultDecl: buildResultDecl(sig, results),
			})
		}
		interfaceData = append(interfaceData, InterfaceData{
			InterfaceName: spec.Name,
			StructName:    "Dummy" + spec.Name,
			Methods:       methods,
		})
	}

	imports := buildImportList(importSet, aliasMap)
	if needsErrorVar {
		imports = append(imports, Import{Name: "", Path: "errors"})
	}

	sort.Slice(imports, func(i, j int) bool {
		return imports[i].Path < imports[j].Path
	})

	data := TemplateData{
		PackageName:   g.options.PackageName,
		Imports:       imports,
		Interfaces:    interfaceData,
		NeedsErrorVar: needsErrorVar,
	}

	rendered, err := renderTemplate(data)
	if err != nil {
		return nil, err
	}

	formatted, err := format.Source(rendered)
	if err != nil {
		return nil, fmt.Errorf("format source: %w\n%s", err, string(rendered))
	}
	return formatted, nil
}

func (g *Generator) resolveInterfaces() ([]InterfaceSpec, error) {
	if len(g.options.Interfaces) > 0 {
		return g.options.Interfaces, nil
	}
	if g.options.Interface != nil && g.options.InterfaceName != "" {
		return []InterfaceSpec{{
			Name:      g.options.InterfaceName,
			Interface: g.options.Interface,
		}}, nil
	}
	return nil, fmt.Errorf("no interfaces provided")
}

func buildParams(sig *types.Signature, qualifier types.Qualifier) []Param {
	params := make([]Param, 0, sig.Params().Len())
	for i := 0; i < sig.Params().Len(); i++ {
		param := sig.Params().At(i)
		name := param.Name()
		if name == "" {
			name = fmt.Sprintf("p%d", i+1)
		}
		params = append(params, Param{
			Name: name,
			Type: types.TypeString(param.Type(), qualifier),
		})
	}
	return params
}

func buildResults(sig *types.Signature, qualifier types.Qualifier) []Result {
	results := make([]Result, 0, sig.Results().Len())
	for i := 0; i < sig.Results().Len(); i++ {
		result := sig.Results().At(i)
		name := result.Name()
		typeStr := types.TypeString(result.Type(), qualifier)
		isError := isErrorType(result.Type())

		zeroValue := zeroValueOf(result.Type(), typeStr)
		if isError {
			zeroValue = "ErrorNotImplemented"
		}

		results = append(results, Result{
			Name:      name,
			Type:      typeStr,
			IsError:   isError,
			ZeroValue: zeroValue,
		})
	}
	return results
}

func buildResultDecl(sig *types.Signature, results []Result) string {
	if sig.Results().Len() == 0 {
		return ""
	}

	hasNames := false
	for _, result := range results {
		if result.Name != "" {
			hasNames = true
			break
		}
	}

	parts := make([]string, 0, len(results))
	for i, result := range results {
		name := result.Name
		if name == "" && hasNames {
			name = fmt.Sprintf("r%d", i+1)
		}
		if name != "" {
			parts = append(parts, fmt.Sprintf("%s %s", name, result.Type))
		} else {
			parts = append(parts, result.Type)
		}
	}

	if len(parts) == 1 && !hasNames {
		return parts[0]
	}
	return fmt.Sprintf("(%s)", strings.Join(parts, ", "))
}

func collectImportsFromSignature(sig *types.Signature, current *types.Package, importSet map[string]Import) {
	recordImportsFromTuple(sig.Params(), current, importSet)
	recordImportsFromTuple(sig.Results(), current, importSet)
}

func recordImportsFromTuple(tuple *types.Tuple, current *types.Package, importSet map[string]Import) {
	if tuple == nil {
		return
	}
	for i := 0; i < tuple.Len(); i++ {
		recordImportsFromType(tuple.At(i).Type(), current, importSet)
	}
}

func recordImportsFromType(t types.Type, current *types.Package, importSet map[string]Import) {
	if t == nil {
		return
	}
	_ = types.TypeString(t, func(pkg *types.Package) string {
		if pkg == nil || pkg.Path() == current.Path() {
			return ""
		}
		importSet[pkg.Path()] = Import{Name: pkg.Name(), Path: pkg.Path()}
		return pkg.Name()
	})
}

func buildAliasMap(importSet map[string]Import) map[string]string {
	paths := make([]string, 0, len(importSet))
	for path := range importSet {
		paths = append(paths, path)
	}
	sort.Strings(paths)

	aliasMap := map[string]string{}
	usedNames := map[string]string{}
	for _, path := range paths {
		imp := importSet[path]
		desired := imp.Name
		if desired == "" {
			desired = pathBase(path)
		}
		alias := desired
		if existing, ok := usedNames[alias]; ok && existing != path {
			suffix := 2
			alias = fmt.Sprintf("%s%d", desired, suffix)
			for {
				if _, exists := usedNames[alias]; !exists {
					break
				}
				suffix++
				alias = fmt.Sprintf("%s%d", desired, suffix)
			}
		}
		usedNames[alias] = path
		aliasMap[path] = alias
	}
	return aliasMap
}

func buildImportList(importSet map[string]Import, aliasMap map[string]string) []Import {
	imports := make([]Import, 0, len(importSet))
	for path, imp := range importSet {
		alias := aliasMap[path]
		name := ""
		if alias != imp.Name {
			name = alias
		}
		imports = append(imports, Import{Name: name, Path: path})
	}
	return imports
}

func pathBase(path string) string {
	if path == "" {
		return ""
	}
	parts := strings.Split(path, "/")
	return parts[len(parts)-1]
}

func isErrorType(t types.Type) bool {
	errObj := types.Universe.Lookup("error")
	if errObj == nil {
		return false
	}
	return types.Identical(t, errObj.Type())
}

func zeroValueOf(t types.Type, typeStr string) string {
	switch typed := t.(type) {
	case *types.Basic:
		return zeroOfBasic(typed)
	case *types.Alias:
		return zeroValueOfAlias(typed, typeStr)
	case *types.Pointer, *types.Slice, *types.Map, *types.Chan, *types.Signature, *types.Interface:
		return "nil"
	case *types.Array, *types.Struct:
		return typeStr + "{}"
	case *types.Named:
		return zeroValueOfNamed(typed, typeStr)
	case *types.TypeParam:
		return "nil"
	default:
		return "nil"
	}
}

func zeroOfBasic(basic *types.Basic) string {
	switch basic.Kind() {
	case types.Bool:
		return "false"
	case types.String:
		return "\"\""
	case types.Int, types.Int8, types.Int16, types.Int32, types.Int64,
		types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.Uintptr:
		return "0"
	case types.Float32, types.Float64, types.Complex64, types.Complex128:
		return "0"
	default:
		return "nil"
	}
}

func zeroValueOfNamed(named *types.Named, typeStr string) string {
	underlying := named.Underlying()
	switch u := underlying.(type) {
	case *types.Basic:
		return fmt.Sprintf("%s(%s)", typeStr, zeroOfBasic(u))
	case *types.Pointer, *types.Slice, *types.Map, *types.Chan, *types.Signature, *types.Interface:
		return "nil"
	case *types.Array, *types.Struct:
		return typeStr + "{}"
	default:
		return "nil"
	}
}

func zeroValueOfAlias(alias *types.Alias, typeStr string) string {
	underlying := alias.Underlying()
	switch u := underlying.(type) {
	case *types.Basic:
		return fmt.Sprintf("%s(%s)", typeStr, zeroOfBasic(u))
	case *types.Pointer, *types.Slice, *types.Map, *types.Chan, *types.Signature, *types.Interface:
		return "nil"
	case *types.Array, *types.Struct:
		return typeStr + "{}"
	default:
		return "nil"
	}
}
