package jsonschema

import (
	"encoding/json"
	"errors"
)

func VerifySchemaAndUnmarshal(schema Definition, content []byte, v any) error {
	var data any
	err := json.Unmarshal(content, &data)
	if err != nil {
		return err
	}
	if !Validate(schema, data) {
		return errors.New("data validation failed against the provided schema")
	}
	return json.Unmarshal(content, &v)
}

func Validate(schema Definition, data any) bool {
	switch schema.Type {
	case Object:
		return validateObject(schema, data)
	case Array:
		return validateArray(schema, data)
	case String:
		_, ok := data.(string)
		return ok
	case Number: // float64 and int
		_, ok := data.(float64)
		if !ok {
			_, ok = data.(int)
		}
		return ok
	case Boolean:
		_, ok := data.(bool)
		return ok
	case Integer:
		// Golang unmarshals all numbers as float64, so we need to check if the float64 is an integer
		if num, ok := data.(float64); ok {
			return num == float64(int64(num))
		}
		_, ok := data.(int)
		return ok
	case Null:
		return data == nil
	default:
		return false
	}
}

func validateObject(schema Definition, data any) bool {
	dataMap, ok := data.(map[string]any)
	if !ok {
		return false
	}
	for _, field := range schema.Required {
		if _, exists := dataMap[field]; !exists {
			return false
		}
	}
	for key, valueSchema := range schema.Properties {
		value, exists := dataMap[key]
		if exists && !Validate(valueSchema, value) {
			return false
		} else if !exists && contains(schema.Required, key) {
			return false
		}
	}
	return true
}

func validateArray(schema Definition, data any) bool {
	dataArray, ok := data.([]any)
	if !ok {
		return false
	}
	for _, item := range dataArray {
		if !Validate(*schema.Items, item) {
			return false
		}
	}
	return true
}

func contains[S ~[]E, E comparable](s S, v E) bool {
	for i := range s {
		if v == s[i] {
			return true
		}
	}
	return false
}
