package collector

import (
	"errors"
	"fmt"

	"github.com/acronis/go-cti/metadata/consts"
	"github.com/acronis/go-raml/v2"
)

func (c *RAMLXCollector) constructAndSetCtiSchemaAnnotation(b *raml.BaseShape, v any) error {
	schemaDefinedBy, err := c.raml.GetReferencedAnnotationType(consts.Schema, b.Location)
	if err != nil {
		return fmt.Errorf("get schema annotation type: %w", err)
	}
	// TODO: Add DomainExtension constructor to go-raml
	b.CustomDomainProperties.Set(consts.Schema, &raml.DomainExtension{
		Name:      consts.Schema,
		DefinedBy: schemaDefinedBy,
		Extension: &raml.Node{Value: v},
		Location:  b.Location,
	})
	return nil
}

func (c *RAMLXCollector) insertCtiSchema(base *raml.BaseShape) (bool, error) {
	if _, ok := base.CustomDomainProperties.Get(consts.Schema); ok {
		return false, nil
	}

	switch t := base.Shape.(type) {
	case *raml.UnionShape:
		values := make([]any, 0, len(t.AnyOf))
		sawCTI := false
		invalid := false

		for _, s := range t.AnyOf {
			ctis, err := c.readMetadataCti(s)
			if err != nil {
				return true, fmt.Errorf("read metadata cti: %w", err)
			}
			_, isNil := s.Shape.(*raml.NilShape)

			switch {
			case len(ctis) > 0:
				values = append(values, ctis[0])
				sawCTI = true
			case isNil:
				values = append(values, nil)
			default:
				invalid = true
			}
		}

		// If no CTIs were found, nothing to do.
		if !sawCTI {
			return false, nil
		}
		// If we saw any non-CTI, non-nil member while there are CTIs - CTI schema will be invalid, skip it.
		if invalid {
			return false, nil
		}
		if err := c.constructAndSetCtiSchemaAnnotation(base, values); err != nil {
			return true, fmt.Errorf("construct schema annotation: %w", err)
		}
		return true, nil
	default:
		target := base
		// Aliases do not have a parent, so we should try to insert into the same type.
		if len(base.Inherits) > 0 {
			target = base.Inherits[0]
		}

		ctis, err := c.readMetadataCti(target)
		if err != nil {
			return true, fmt.Errorf("read metadata cti: %w", err)
		}
		if len(ctis) == 0 {
			return false, nil
		}
		if err := c.constructAndSetCtiSchemaAnnotation(base, ctis[0]); err != nil {
			return true, fmt.Errorf("construct schema annotation: %w", err)
		}
		return true, nil
	}
}

func (c *RAMLXCollector) insertImplicitSchema(base *raml.BaseShape, isRoot bool) error {
	if !isRoot {
		stop, err := c.insertCtiSchema(base)
		if err != nil {
			return fmt.Errorf("insert cti schema: %w", err)
		}
		if stop {
			return nil
		}
	}

	// If schema does not have cti.cti, then we continue traverse
	switch t := base.Shape.(type) {
	case *raml.ArrayShape:
		if t.Items != nil {
			if err := c.insertImplicitSchema(t.Items, false); err != nil {
				return fmt.Errorf("array items: %w", err)
			}
		}
	case *raml.ObjectShape:
		for pair := t.Properties.Oldest(); pair != nil; pair = pair.Next() {
			if err := c.insertImplicitSchema(pair.Value.Base, false); err != nil {
				return fmt.Errorf("pattern property %s: %w", pair.Key, err)
			}
		}
		for pair := t.PatternProperties.Oldest(); pair != nil; pair = pair.Next() {
			if err := c.insertImplicitSchema(pair.Value.Base, false); err != nil {
				return fmt.Errorf("pattern property %s: %w", pair.Key, err)
			}
		}
	case *raml.UnionShape:
		for _, s := range t.AnyOf {
			if err := c.insertImplicitSchema(s, false); err != nil {
				return fmt.Errorf("union anyof: %w", err)
			}
		}
	}

	return nil
}

func (c *RAMLXCollector) unwrapMetadataType(base *raml.BaseShape) (*raml.BaseShape, error) {
	s := base.Shape
	if s == nil {
		return nil, errors.New("shape is nil")
	}

	// Skip already unwrapped shapes
	if base.IsUnwrapped() {
		return base, nil
	}
	base.SetUnwrapped()

	var source *raml.BaseShape
	if base.Alias != nil {
		us, err := c.unwrapMetadataType(base.Alias)
		if err != nil {
			return nil, fmt.Errorf("alias unwrap: %w", err)
		}
		return base.AliasTo(us)
	}

	switch {
	case base.Link != nil:
		us, err := c.unwrapMetadataType(base.Link.Shape)
		if err != nil {
			return nil, fmt.Errorf("link unwrap: %w", err)
		}
		source = us

		base.Link = nil
	case len(base.Inherits) == 1:
		parent := base.Inherits[0]
		// Continue unwrapping non-CTI type
		if _, ok := parent.CustomDomainProperties.Get(consts.CTI); !ok {
			ss, err := c.unwrapMetadataType(parent)
			if err != nil {
				return nil, fmt.Errorf("parent unwrap: %w", err)
			}
			source = ss
		}
	case len(base.Inherits) > 1:
		inherits := base.Inherits
		ctiInherits, ramlInherits := c.splitMultipleInherits(inherits)
		if len(ctiInherits) > 1 {
			return nil, errors.New("multiple CTI inheritance is not supported")
		}
		if len(ramlInherits) > 0 {
			ss, err := c.inheritMultipleRamlParents(ramlInherits)
			if err != nil {
				return nil, fmt.Errorf("inherit multiple raml parents: %w", err)
			}
			source = ss
		}
	}

	if err := c.raml.UnwrapTarget(s); err != nil {
		return nil, fmt.Errorf("unwrap target: %w", err)
	}

	for pair := base.CustomShapeFacetDefinitions.Oldest(); pair != nil; pair = pair.Next() {
		prop := pair.Value
		us, err := c.raml.UnwrapShape(prop.Base)
		if err != nil {
			return nil, fmt.Errorf("custom shape facet definition unwrap: %w", err)
		}
		prop.Base = us
		base.CustomShapeFacetDefinitions.Set(pair.Key, prop)
	}

	if source != nil {
		is, errInherit := base.Inherit(source)
		if errInherit != nil {
			return nil, fmt.Errorf("merge shapes: %w", errInherit)
		}
		return is, nil
	}
	return base, nil
}

func (c *RAMLXCollector) inheritMultipleRamlParents(inherits []*raml.BaseShape) (*raml.BaseShape, error) {
	ss, err := c.unwrapMetadataType(inherits[0])
	if err != nil {
		return nil, fmt.Errorf("parent unwrap: %w", err)
	}
	for i := 1; i < len(inherits); i++ {
		us, err := c.unwrapMetadataType(inherits[i])
		if err != nil {
			return nil, fmt.Errorf("parent unwrap: %w", err)
		}
		_, err = ss.Inherit(us)
		if err != nil {
			return nil, fmt.Errorf("inherit shapes: %w", err)
		}
	}
	return ss, nil
}

func (c *RAMLXCollector) splitMultipleInherits(inherits []*raml.BaseShape) ([]*raml.BaseShape, []*raml.BaseShape) {
	ramlInherits := make([]*raml.BaseShape, 0, len(inherits))
	ctiInherits := make([]*raml.BaseShape, 0, len(inherits))
	// Multiple parents are aliased
	for _, inherit := range inherits {
		if _, ok := inherit.Alias.CustomDomainProperties.Get(consts.CTI); ok {
			ctiInherits = append(ctiInherits, inherit.Alias)
		} else {
			ramlInherits = append(ramlInherits, inherit.Alias)
		}
	}
	return ctiInherits, ramlInherits
}
