package app

import (
	appleError "core/error"
	"errors"
	"fmt"
	"strings"

	"go.mongodb.org/mongo-driver/bson/primitive"
)

//CreateTable create a new table
func (a *App) CreateTable(tableID, displayName string, description string,
	fields map[string]Field) error {
	if a.Tables == nil {
		a.Tables = map[string]Table{}
	}
	if _, tableExist := a.Tables[tableID]; tableExist {
		return appleError.New(appleError.GenerateErrorCode(appleError.Table,
			appleError.TableExistsErrorCode), appleError.TableError,
			appleError.TableExist)
	}
	newTable := NewTable(tableID, displayName, description, fields)
	a.Tables[newTable.ID] = *newTable
	return nil
}

//UpdateTable update table
func (a *App) UpdateTable(tableID, displayName string, description string, fields map[string]Field) error {
	if _, tableExist := a.Tables[tableID]; !tableExist {
		return appleError.New(appleError.GenerateErrorCode(appleError.Table,
			appleError.InvalidTableErrorCode), appleError.TableError,
			appleError.InvalidTable)
	}
	existingTable := a.Tables[tableID]
	existingTable.Description = description
	existingTable.DisplayName = displayName
	existingTable.Fields = fields
	a.Tables[tableID] = existingTable
	return nil
}

//DeleteTable delete table
func (a *App) DeleteTable(tableID string) error {
	if _, tableExist := a.Tables[tableID]; !tableExist {
		return appleError.New(appleError.GenerateErrorCode(appleError.Table,
			appleError.InvalidTableErrorCode), appleError.TableError,
			appleError.InvalidTable)
	}
	delete(a.Tables, tableID)
	return nil
}

func (a *App) CreatTablesFieldsFromSchema(schema map[string]interface{}) (map[string]Field, error) {
	if _, typeExists := schema["type"]; !typeExists {
		return nil, errors.New("Schema type not defined")
	}
	if _, propertiesExists := schema["properties"]; !propertiesExists {
		return nil, errors.New("Schema properties not defined")
	}
	if _, isMap := schema["properties"].(map[string]interface{}); !isMap {
		return nil, errors.New("Schema properties not a map")
	}
	schemaProperties := schema["properties"].(map[string]interface{})
	fields := map[string]Field{}
	for propertyName, property := range schemaProperties {
		if propertyName == "id" {
			continue
		}
		if len(strings.TrimSpace(propertyName)) == 0 {
			return nil, errors.New(appleError.EmptyFlowFieldName)
		}
		fieldType, err := a.getTableFieldTypeFromProperty(property.(map[string]interface{}))
		if err != nil {
			return nil, err
		}
		newfield := NewField(primitive.NewObjectID().Hex(), propertyName,
			getFieldDescription(property.(map[string]interface{})), fieldType)
		fields[newfield.ID] = *newfield
	}
	return fields, nil
}

func (a *App) getTableFieldTypeFromProperty(property map[string]interface{}) (DataType, error) {
	if _, typeExists := property["type"]; !typeExists {
		return "", errors.New("Property type not defined")
	}
	if _, isString := property["type"].(string); !isString {
		return "", errors.New("Property type not string")
	}
	propertyType := property["type"].(string)
	switch propertyType {
	case "integer":
		return Number, nil
	case "number":
		return Number, nil
	case "boolean":
		return Boolean, nil
	case "string":
		if _, stringFormatExists := property["format"]; !stringFormatExists {
			if _, parseAsExists := property["parseAs"]; parseAsExists {
				parseAsValue := property["parseAs"].(string)
				if parseAsValue == "singleLineText" {
					return Text, nil
				}
				if parseAsValue == "multilineText" {
					return MultiLineText, nil
				}
				if parseAsValue == "singleSelect" {
					return SingleSelect, nil
				}
				return SingleLineText, nil
			}
			return Text, nil
		}
		formatValue := property["format"].(string)
		if formatValue == "date-time" {
			return Date, nil
		}
	case "array":
		if _, stringFormatExists := property["parseAs"]; !stringFormatExists {
			return Array, nil
		}
		parseAsValue := property["parseAs"].(string)
		if parseAsValue == "collaborator" {
			return Collaborator, nil
		}
		if parseAsValue == "attachment" {
			return Attachment, nil
		}
		if parseAsValue == "multiSelect" {
			return MultiSelect, nil
		}
		return Array, nil
	case "object":
		return ObjectType, nil
	default:
		return "", errors.New("Cannot create field from given property type")
	}
	return "", nil
}

func getFieldDescription(fieldProperty map[string]interface{}) string {
	if _, descriptionExists := fieldProperty["description"]; !descriptionExists {
		return ""
	}
	if _, isString := fieldProperty["description"].(string); isString {
		return fieldProperty["description"].(string)
	}
	return ""
}

func (a *App) CreateUniqueTableName(controlDisplayName string) string {
	controlAppendedTableName := getFormattedString(tableNameUsingControlName,
		controlDisplayName, "table")
	if a.Tables == nil {
		return controlAppendedTableName
	}
	displayNameMapedTable := map[string]Table{}
	for _, table := range a.Tables {
		displayNameMapedTable[table.DisplayName] = table
	}

	if _, tableNameExists := displayNameMapedTable[controlAppendedTableName]; !tableNameExists {
		return controlAppendedTableName
	} else {
		for i := 1; ; i++ {
			newTableName := getFormattedString(tableNameFormat, controlAppendedTableName, i)
			if _, tableNameExists := displayNameMapedTable[newTableName]; tableNameExists {
				continue
			}
			return newTableName
		}
	}

}

const (
	DevTableIdFormat string = "%s%s"
)

func GetDevTableId(tableId string) string {
	return fmt.Sprintf(DevTableIdFormat, DevTableIdPrefix, tableId)
}

func GetInferredTableId(appId, tableId string) string {
	if IsDevAppID(appId) {
		return GetDevTableId(tableId)
	}
	return tableId
}
