// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package identity

import (
	"os"
	"path/filepath"

	"github.com/zeebo/errs"

	"storj.io/common/fpath"
)

// TLSFilesStatus is the status of keys.
type TLSFilesStatus int

// Four possible outcomes for four files.
const (
	NoCertNoKey = TLSFilesStatus(iota)
	CertNoKey
	NoCertKey
	CertKey
)

var (
	// ErrZeroBytes is returned for zero slice.
	ErrZeroBytes = errs.New("byte slice was unexpectedly empty")
)

// writeChainData writes data to path ensuring permissions are appropriate for a cert.
func writeChainData(path string, data []byte) error {
	err := writeFile(path, 0744, 0644, data)
	if err != nil {
		return errs.New("unable to write certificate to \"%s\": %v", path, err)
	}
	return nil
}

// writeKeyData writes data to path ensuring permissions are appropriate for a cert.
func writeKeyData(path string, data []byte) error {
	err := writeFile(path, 0700, 0600, data)
	if err != nil {
		return errs.New("unable to write key to \"%s\": %v", path, err)
	}
	return nil
}

// writeFile writes to path, creating directories and files with the necessary permissions.
func writeFile(path string, dirmode, filemode os.FileMode, data []byte) error {
	if err := os.MkdirAll(filepath.Dir(path), dirmode); err != nil {
		return errs.Wrap(err)
	}
	if writable, err := fpath.IsWritable(filepath.Dir(path)); !writable || err != nil {
		return errs.Wrap(errs.New("%s is not a writeable directory: %s\n", path, err))
	}
	if err := os.WriteFile(path, data, filemode); err != nil {
		return errs.Wrap(err)
	}

	return nil
}

func statTLSFiles(certPath, keyPath string) (status TLSFilesStatus, err error) {
	hasKey := true
	hasCert := true

	_, err = os.Stat(certPath)
	if err != nil {
		if os.IsNotExist(err) {
			hasCert = false
		} else {
			return NoCertNoKey, err
		}
	}

	_, err = os.Stat(keyPath)
	if err != nil {
		if os.IsNotExist(err) {
			hasKey = false
		} else {
			return NoCertNoKey, err
		}
	}
	switch {
	case hasCert && hasKey:
		return CertKey, nil
	case hasCert:
		return CertNoKey, nil
	case hasKey:
		return NoCertKey, nil
	}

	return NoCertNoKey, nil
}

func (t TLSFilesStatus) String() string {
	switch t {
	case CertKey:
		return "certificate and key"
	case CertNoKey:
		return "certificate"
	case NoCertKey:
		return "key"
	}
	return ""
}
