package common

import (
	"errors"
	"fmt"
	"github.com/fatih/structtag"
	"go/ast"
)

func isPointerType(typeExpr ast.Expr) bool {
	isPointer := false
	if _, ok := typeExpr.(*ast.StarExpr); ok {
		isPointer = true
	}
	return isPointer
}

func isGormValueNullable(tags *structtag.Tags) (*bool, error) {
	gormTag, err := tags.Get("gorm")
	if gormTag == nil {
		return nil, nil
	}

	if err != nil {
		return nil, nil
	}

	gormTag.Options = append([]string{gormTag.Name}, gormTag.Options...)

	nullTagExist := gormTag.HasOption("null")
	notNullTagExist := gormTag.HasOption("not null")

	if nullTagExist == notNullTagExist && nullTagExist {
		return nil, errors.New(`tags "null" and "not null" are specified at one field`)
	}

	if nullTagExist {
		return PointerOf(true), nil
	} else if notNullTagExist {
		return PointerOf(false), nil
	} else {
		return PointerOf(false), nil
	}
}

func CheckFieldNullConsistency(field ast.Field, fieldName string, structName string, structTags string) error {
	tags, err := structtag.Parse(structTags)
	if err != nil {
		return errors.New(fmt.Sprintf("Invalid structure tag: %s", err))
	}

	if tags == nil {
		return nil
	}

	isFieldNullable := isPointerType(field.Type)
	isColumnNullable, err := isGormValueNullable(tags)

	if err != nil {
		return errors.New(fmt.Sprintf("Null safety error: %s", err))
	}
	if isColumnNullable == nil {
		return nil
	}

	if isFieldNullable != *isColumnNullable {
		return errors.New(fmt.Sprintf("Null safety error in \"%s\" model, field \"%s\": column nullable policy doesn't match to tag nullable policy", structName, fieldName))
	}
	return nil
}
