// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package logtail

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

// PrivateID represents an instance that write logs.
// Private IDs are only shared with the server when writing logs.
type PrivateID [32]byte

// Safely generate a new PrivateId for use in Config objects.
// You should persist this across runs of an instance of your app, so that
// it can append to the same log file on each run.
func NewPrivateID() (id PrivateID, err error) {
	_, err = rand.Read(id[:])
	if err != nil {
		return PrivateID{}, err
	}
	// Clamping, for future use.
	id[0] &= 248
	id[31] = (id[31] & 127) | 64
	return id, nil
}

func (id PrivateID) MarshalText() ([]byte, error) {
	b := make([]byte, hex.EncodedLen(len(id)))
	if i := hex.Encode(b, id[:]); i != len(b) {
		return nil, fmt.Errorf("logtail.PrivateID.MarhsalText: i=%d", i)
	}
	return b, nil
}

func (id *PrivateID) UnmarshalText(s []byte) error {
	b, err := hex.DecodeString(string(s))
	if err != nil {
		return fmt.Errorf("logtail.PrivateID.UnmarshalText: %v", err)
	}
	if len(b) != len(id) {
		return fmt.Errorf("logtail.PrivateID.UnmarshalText: invalid hex length: %d", len(b))
	}
	copy(id[:], b)
	return nil
}

func (id PrivateID) String() string {
	b, err := id.MarshalText()
	if err != nil {
		panic(err)
	}
	return string(b)
}

func (id PrivateID) Public() (pub PublicID) {
	var emptyID PrivateID
	if id == emptyID {
		panic("invalid logtail.Public() on an empty private ID")
	}
	h := sha256.New()
	h.Write(id[:])
	if n := copy(pub[:], h.Sum(pub[:0])); n != len(pub) {
		panic(fmt.Sprintf("public id short copy: %d", n))
	}
	return pub
}

// PublicID represents an instance in the logs service for reading and adoption.
// The public ID value is a SHA-256 hash of a private ID.
type PublicID [sha256.Size]byte

func (id PublicID) MarshalText() ([]byte, error) {
	b := make([]byte, hex.EncodedLen(len(id)))
	if i := hex.Encode(b, id[:]); i != len(b) {
		return nil, fmt.Errorf("logtail.PublicID.MarhsalText: i=%d", i)
	}
	return b, nil
}

func (id *PublicID) UnmarshalText(s []byte) error {
	b, err := hex.DecodeString(string(s))
	if err != nil {
		return fmt.Errorf("logtail.PublicID.UnmarshalText: %v", err)
	}
	if len(b) != len(id) {
		return fmt.Errorf("logtail.PublicID.UnmarshalText: invalid hex length: %d", len(b))
	}
	copy(id[:], b)
	return nil
}

func (id PublicID) String() string {
	b, err := id.MarshalText()
	if err != nil {
		panic(err)
	}
	return string(b)
}