package apiv1

import (
	"errors"
	"time"

	"code.secondbit.org/api.hg"
	"code.secondbit.org/ducky/devices.hg"
	"code.secondbit.org/uuid.hg"
)

var (
	errUnauthorizedLastSeen = errors.New("not authorized to set last seen")
	errUnauthorizedCreated  = errors.New("not authorized to set created")
	errUnauthorizedOwner    = errors.New("not authorized to set owner")
	errInvalidDeviceType    = errors.New("device type invalid")
	errDeviceNameTooShort   = errors.New("device name too short")
	errDeviceNameTooLong    = errors.New("device name too long")
)

// Device represents a device as exposed through the API. It is its
// own type as the business logic and the API have different requirements
// for the Device type, and require different representations.
type Device struct {
	ID        uuid.ID            `json:"id"`
	Name      string             `json:"name,omitempty"`
	Owner     uuid.ID            `json:"owner,omitempty"`
	Type      devices.DeviceType `json:"type,omitempty"`
	Created   time.Time          `json:"created,omitempty"`
	LastSeen  time.Time          `json:"lastSeen,omitempty"`
	PushToken string             `json:"pushToken,omitempty"`
}

// DeviceChange represents a set of changes to a device that will be used
// to update that device. It is its own type as the business logic and the
// API have different requirements for the DeviceChange type, and require
// different representations.
type DeviceChange struct {
	DeviceID  uuid.ID             `json:"id"`
	Name      *string             `json:"name,omitempty"`
	Type      *devices.DeviceType `json:"type,omitempty"`
	LastSeen  *time.Time          `json:"lastSeen,omitempty"`
	PushToken *string             `json:"pushToken,omitempty"`
}

func apiDeviceFromCore(d devices.Device, includePushToken bool) Device {
	device := Device{
		ID:       d.ID,
		Name:     d.Name,
		Type:     d.Type,
		Created:  d.Created,
		LastSeen: d.LastSeen,
		Owner:    d.Owner,
	}
	if includePushToken {
		device.PushToken = d.PushToken
	}
	return device
}

func deviceFromAPI(d Device) devices.Device {
	return devices.Device{
		ID:        d.ID,
		Name:      d.Name,
		Type:      d.Type,
		Created:   d.Created,
		LastSeen:  d.LastSeen,
		PushToken: d.PushToken,
		Owner:     d.Owner,
	}
}

func devicesFromAPI(in []Device) []devices.Device {
	out := make([]devices.Device, 0, len(in))
	for _, d := range in {
		out = append(out, deviceFromAPI(d))
	}
	return out
}

func changeFromAPI(d DeviceChange) devices.DeviceChange {
	return devices.DeviceChange{
		DeviceID:  d.DeviceID,
		Name:      d.Name,
		Type:      d.Type,
		LastSeen:  d.LastSeen,
		PushToken: d.PushToken,
	}
}

func createDevicesFromChanges(changes []DeviceChange) []devices.Device {
	newDevices := make([]devices.Device, 0, len(changes))
	for _, change := range changes {
		newDevices = append(newDevices, devices.ApplyChange(devices.Device{}, changeFromAPI(change)))
	}
	return newDevices
}

func validateDeviceCreation(d devices.Device, scopes []string, user uuid.ID) error {
	canImport := api.CheckScopes(scopes, ScopeImport.ID)
	canCreateOtherUserDevices := api.CheckScopes(scopes, ScopeCreateOtherUserDevices.ID)
	if !d.LastSeen.IsZero() && !canImport {
		return errUnauthorizedLastSeen
	}
	if !d.Created.IsZero() && !canImport {
		return errUnauthorizedCreated
	}
	if !d.Owner.Equal(user) && !canCreateOtherUserDevices {
		return errUnauthorizedOwner
	}
	if !devices.IsValidDeviceType(d.Type) {
		return errInvalidDeviceType
	}
	if len(d.Name) < devices.MinDeviceNameLength {
		return errDeviceNameTooShort
	}
	if len(d.Name) > devices.MaxDeviceNameLength {
		return errDeviceNameTooLong
	}
	return nil
}

func validateDeviceUpdate(d Device, change DeviceChange, scopes []string, user uuid.ID) error {
	canUpdateOtherUserDevices := api.CheckScopes(scopes, ScopeUpdateOtherUserDevices.ID)
	canUpdateLastSeen := api.CheckScopes(scopes, ScopeUpdateLastSeen.ID)

	if !d.Owner.Equal(user) && !canUpdateOtherUserDevices {
		return errUnauthorizedOwner
	}
	if change.LastSeen != nil && !change.LastSeen.IsZero() && !canUpdateLastSeen {
		return errUnauthorizedLastSeen
	}
	if change.Type != nil && !devices.IsValidDeviceType(*change.Type) {
		return errInvalidDeviceType
	}
	if change.Name != nil && len(*change.Name) < devices.MinDeviceNameLength {
		return errDeviceNameTooShort
	}
	if change.Name != nil && len(*change.Name) > devices.MaxDeviceNameLength {
		return errDeviceNameTooLong
	}

	return nil
}
