package local

import (
	"fmt"
	"math"
	"os"
	"runtime"
	"strconv"
	"time"

	"github.com/rclone/rclone/fs"
)

const metadataTimeFormat = time.RFC3339Nano

// system metadata keys which this backend owns
//
// not all values supported on all OSes
var systemMetadataInfo = map[string]fs.MetadataHelp{
	"mode": {
		Help:    "File type and mode",
		Type:    "octal, unix style",
		Example: "0100664",
	},
	"uid": {
		Help:    "User ID of owner",
		Type:    "decimal number",
		Example: "500",
	},
	"gid": {
		Help:    "Group ID of owner",
		Type:    "decimal number",
		Example: "500",
	},
	"rdev": {
		Help:    "Device ID (if special file)",
		Type:    "hexadecimal",
		Example: "1abc",
	},
	"atime": {
		Help:    "Time of last access",
		Type:    "RFC 3339",
		Example: "2006-01-02T15:04:05.999999999Z07:00",
	},
	"mtime": {
		Help:    "Time of last modification",
		Type:    "RFC 3339",
		Example: "2006-01-02T15:04:05.999999999Z07:00",
	},
	"btime": {
		Help:    "Time of file birth (creation)",
		Type:    "RFC 3339",
		Example: "2006-01-02T15:04:05.999999999Z07:00",
	},
}

// parse a time string from metadata with key
func (o *Object) parseMetadataTime(m fs.Metadata, key string) (t time.Time, ok bool) {
	value, ok := m[key]
	if ok {
		var err error
		t, err = time.Parse(metadataTimeFormat, value)
		if err != nil {
			fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
			ok = false
		}
	}
	return t, ok
}

// parse am int from metadata with key and base
func (o *Object) parseMetadataInt(m fs.Metadata, key string, base int) (result int, ok bool) {
	value, ok := m[key]
	if ok {
		var err error
		parsed, err := strconv.ParseInt(value, base, 0)
		if err != nil {
			fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
			ok = false
		}
		result = int(parsed)
	}
	return result, ok
}

// Write the metadata into the file
//
// It isn't possible to set the ctime and btime under Unix
func (o *Object) writeMetadataToFile(m fs.Metadata) (outErr error) {
	var err error
	atime, atimeOK := o.parseMetadataTime(m, "atime")
	mtime, mtimeOK := o.parseMetadataTime(m, "mtime")
	btime, btimeOK := o.parseMetadataTime(m, "btime")
	if atimeOK || mtimeOK {
		if atimeOK && !mtimeOK {
			mtime = atime
		}
		if !atimeOK && mtimeOK {
			atime = mtime
		}
		err = o.setTimes(atime, mtime)
		if err != nil {
			outErr = fmt.Errorf("failed to set times: %w", err)
		}
	}
	if haveSetBTime {
		if btimeOK {
			if o.translatedLink {
				err = lsetBTime(o.path, btime)
			} else {
				err = setBTime(o.path, btime)
			}
			if err != nil {
				outErr = fmt.Errorf("failed to set birth (creation) time: %w", err)
			}
		}
	}
	uid, hasUID := o.parseMetadataInt(m, "uid", 10)
	gid, hasGID := o.parseMetadataInt(m, "gid", 10)
	if hasUID {
		// FIXME should read UID and GID of current user and only attempt to set it if different
		if !hasGID {
			gid = uid
		}
		if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
			fs.Debugf(o, "Ignoring request to set ownership %o.%o on this OS", gid, uid)
		} else {
			if o.translatedLink {
				err = os.Lchown(o.path, uid, gid)
			} else {
				err = os.Chown(o.path, uid, gid)
			}
			if err != nil {
				outErr = fmt.Errorf("failed to change ownership: %w", err)
			}
		}
	}
	mode, hasMode := o.parseMetadataInt(m, "mode", 8)
	if hasMode {
		if mode >= 0 {
			umode := uint(mode)
			if umode <= math.MaxUint32 {
				if o.translatedLink {
					if haveLChmod {
						err = lChmod(o.path, os.FileMode(umode))
					} else {
						fs.Debugf(o, "Unable to set mode %v on a symlink on this OS", os.FileMode(umode))
						err = nil
					}
				} else {
					err = os.Chmod(o.path, os.FileMode(umode))
				}
				if err != nil {
					outErr = fmt.Errorf("failed to change permissions: %w", err)
				}
			}
		}
	}
	// FIXME not parsing rdev yet
	return outErr
}
