package codegen

import (
	"fmt"
	"reflect"
	"slices"
	"sort"
	"strings"

	"github.com/sanity-io/litter"

	"github.com/atombender/go-jsonschema/pkg/schemas"
)

var ErrCannotGenerateCodegenPackageContentError = fmt.Errorf("cannot generate codegen.Package content")

type Decl interface {
	Generate(out *Emitter) error
}

type Named interface {
	Decl
	GetName() string
}

type File struct {
	FileName string
	Package  Package
}

func (p *File) Generate(out *Emitter) error {
	out.Comment("Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.")
	out.Newline()

	if err := p.Package.Generate(out); err != nil {
		return fmt.Errorf("cannot generate codegen.File content: %w", err)
	}

	return nil
}

// Package is a "package <name>; <body>".
type Package struct {
	QualifiedName string
	Comment       string
	Decls         []Decl
	Imports       []Import
}

func (p *Package) AddDecl(d Decl) {
	if !p.hasDecl(d) {
		p.Decls = append(p.Decls, d)
	}
}

func (p *Package) hasDecl(d Decl) bool {
	n1, ok1 := d.(Named)

	for _, pd := range p.Decls {
		if pd == d || reflect.DeepEqual(pd, d) {
			return true
		}

		n2, ok2 := pd.(Named)
		if ok1 && ok2 && n1.GetName() == n2.GetName() {
			return true
		}
	}

	return false
}

func (p *Package) AddImport(qualifiedName, alias string) {
	if !p.hasImport(qualifiedName) {
		p.Imports = append(p.Imports, Import{
			QualifiedName: qualifiedName,
			Name:          alias,
		})
	}
}

func (p *Package) hasImport(q string) bool {
	for _, i := range p.Imports {
		if i.QualifiedName == q {
			return true
		}
	}

	return false
}

func (p *Package) Name() string {
	s := p.QualifiedName
	if i := strings.LastIndex(s, "/"); i != -1 && i < len(s)-1 {
		return s[i+1:]
	}

	return s
}

func (p *Package) Generate(out *Emitter) error {
	out.Comment(p.Comment)
	out.Printlnf("package %s", p.Name())

	if len(p.Imports) > 0 {
		slices.SortStableFunc(p.Imports, func(i, j Import) int {
			if i.QualifiedName < j.QualifiedName {
				return -1
			}

			if i.QualifiedName > j.QualifiedName {
				return 1
			}

			return 0
		})

		for _, i := range p.Imports {
			if err := i.Generate(out); err != nil {
				return fmt.Errorf("%w: %w", ErrCannotGenerateCodegenPackageContentError, err)
			}
		}
	}

	out.Newline()

	sorted := make([]Decl, len(p.Decls))
	copy(sorted, p.Decls)
	sort.Slice(sorted, func(i, j int) bool {
		if a, ok := sorted[i].(Named); ok {
			if b, ok := sorted[j].(Named); ok {
				return schemas.CleanNameForSorting(a.GetName()) < schemas.CleanNameForSorting(b.GetName())
			}
		}

		return false
	})

	for i, t := range sorted {
		if i > 0 {
			out.Newline()
		}

		if err := t.Generate(out); err != nil {
			return fmt.Errorf("%w: %w", ErrCannotGenerateCodegenPackageContentError, err)
		}
	}

	return nil
}

// Var is a "var <name> = <value>".
type Var struct {
	Type  Type
	Name  string
	Value any
}

func (v *Var) GetName() string {
	return v.Name
}

func (v *Var) Generate(out *Emitter) error {
	out.Printf("var %s ", v.Name)

	if v.Type != nil {
		if err := v.Type.Generate(out); err != nil {
			return fmt.Errorf("cannot generate codegen.Var content: %w", err)
		}
	}

	out.Printf(" = %s", litter.Sdump(v.Value))

	return nil
}

// Constant is a "const <name> = <value>".
type Constant struct {
	Type  Type
	Name  string
	Value any
}

func (c *Constant) GetName() string {
	return c.Name
}

func (c *Constant) Generate(out *Emitter) error {
	out.Printf("const %s ", c.Name)

	if c.Type != nil {
		if err := c.Type.Generate(out); err != nil {
			return fmt.Errorf("cannot generate codegen.Constant content: %w", err)
		}
	}

	out.Printf(" = %s", litter.Sdump(c.Value))

	return nil
}

// Fragment is an arbitrary piece of code.
type Fragment func(*Emitter)

func (f Fragment) Generate(out *Emitter) error {
	f(out)

	return nil
}

// Method defines a method and how to generate it.
type Method struct {
	Impl func(*Emitter) error
	Name string
}

func (m *Method) GetName() string {
	return m.Name
}

func (m *Method) Generate(out *Emitter) error {
	out.Newline()

	if err := m.Impl(out); err != nil {
		return fmt.Errorf("cannot generate method %s: %w", m.Name, err)
	}

	out.Newline()

	return nil
}

// Import is a "type <name> = <definition>".
type Import struct {
	Name          string
	QualifiedName string
}

func (i *Import) Generate(out *Emitter) error {
	if i.Name != "" {
		out.Printlnf("import %s %q", i.Name, i.QualifiedName)
	} else {
		out.Printlnf("import %q", i.QualifiedName)
	}

	return nil
}

// TypeDecl is a "type <name> = <definition>".
type TypeDecl struct {
	Name       string
	Type       Type
	Comment    string
	SchemaType *schemas.Type
}

func (td *TypeDecl) GetName() string {
	return td.Name
}

func (td *TypeDecl) Generate(out *Emitter) error {
	out.Comment(td.Comment)
	out.Printf("type %s ", td.Name)

	if err := td.Type.Generate(out); err != nil {
		return fmt.Errorf("cannot generate codegen.TypeDecl content: %w", err)
	}

	out.Newline()

	return nil
}

type Type interface {
	Decl
	IsNillable() bool
}

type AliasType struct {
	Decl

	Alias string
	Name  string
}

func (p AliasType) Generate(out *Emitter) error {
	out.Printlnf("type %s = %s", p.Alias, p.Name)

	return nil
}

type PointerType struct {
	Type Type
}

func (PointerType) IsNillable() bool { return true }

func (p PointerType) Generate(out *Emitter) error {
	out.Printf("*")

	if err := p.Type.Generate(out); err != nil {
		return fmt.Errorf("cannot generate codegen.PointerType content: %w", err)
	}

	return nil
}

type ArrayType struct {
	Type Type
}

func (ArrayType) IsNillable() bool { return true }

func (a ArrayType) Generate(out *Emitter) error {
	out.Printf("[]")

	if err := a.Type.Generate(out); err != nil {
		return fmt.Errorf("cannot generate codegen.ArrayType content: %w", err)
	}

	return nil
}

type NamedType struct {
	Package *Package
	Decl    *TypeDecl
}

func (t NamedType) GetName() string {
	return t.Decl.Name
}

func (t NamedType) IsNillable() bool {
	return t.Decl.Type != nil && t.Decl.Type.IsNillable()
}

func (t NamedType) Generate(out *Emitter) error {
	if t.Package != nil {
		out.Printf("%s", t.Package.Name())
		out.Printf(".")
	}

	out.Printf("%s", t.Decl.Name)

	return nil
}

// DurationType exists to provide special handling when validating durations.
// Having a dedicated type for durations allows the validator to
// differentiate between durations and other types.
type DurationType struct{}

func (t DurationType) GetName() string {
	return "Duration"
}

func (t DurationType) IsNillable() bool {
	return false
}

func (t DurationType) Generate(out *Emitter) error {
	out.Printf("time.Duration")

	return nil
}

type PrimitiveType struct {
	Type string
}

func (PrimitiveType) IsNillable() bool { return false }

func (p PrimitiveType) Generate(out *Emitter) error {
	out.Printf("%s", p.Type)

	return nil
}

type CustomNameType struct {
	Type     string
	Nillable bool
}

func (p CustomNameType) IsNillable() bool { return p.Nillable }

func (p CustomNameType) Generate(out *Emitter) error {
	out.Printf("%s", p.Type)

	return nil
}

type MapType struct {
	KeyType, ValueType Type
}

func (MapType) IsNillable() bool { return true }

func (p MapType) Generate(out *Emitter) error {
	out.Printf("map[")

	if kerr := p.KeyType.Generate(out); kerr != nil {
		return fmt.Errorf("cannot generate codegen.MapType key type: %w", kerr)
	}

	out.Printf("]")

	if perr := p.ValueType.Generate(out); perr != nil {
		return fmt.Errorf("cannot generate codegen.MapType value type: %w", perr)
	}

	return nil
}

type EmptyInterfaceType struct{}

func (EmptyInterfaceType) IsNillable() bool { return true }

func (EmptyInterfaceType) Generate(out *Emitter) error {
	out.Printf("interface{}")

	return nil
}

type NullType struct{}

func (NullType) IsNillable() bool { return true }

func (NullType) Generate(out *Emitter) error {
	out.Printf("interface{}")

	return nil
}

type StructType struct {
	Fields             []StructField
	RequiredJSONFields []string
	DefaultValue       any
}

func (*StructType) IsNillable() bool { return false }

func (s *StructType) AddField(f StructField) {
	s.Fields = append(s.Fields, f)
}

func (s *StructType) Generate(out *Emitter) error {
	out.Printlnf("struct {")
	out.Indent(1)

	i := 0

	for _, f := range s.Fields {
		if i > 0 {
			out.Newline()
		}

		if err := f.Generate(out); err != nil {
			return fmt.Errorf("cannot generate codegen.StructType field: %w", err)
		}

		out.Newline()

		i++
	}

	out.Indent(-1)
	out.Printf("}")

	return nil
}

type StructField struct {
	Name         string
	Type         Type
	Comment      string
	Tags         string
	JSONName     string
	DefaultValue any
	SchemaType   *schemas.Type
}

func (f *StructField) GetName() string {
	return f.Name
}

func (f *StructField) Generate(out *Emitter) error {
	out.Comment(f.Comment)
	out.Printf("%s ", f.Name)

	if err := f.Type.Generate(out); err != nil {
		return fmt.Errorf("cannot generate codegen.StructField content: %w", err)
	}

	if f.Tags != "" {
		out.Printf(" `%s`", f.Tags)
	}

	return nil
}
