package group

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"hash"

	"golang.org/x/crypto/pbkdf2"

	"github.com/jech/galene/conn"
)

type RawPassword struct {
	Type       string `json:"type,omitempty"`
	Hash       string `json:"hash,omitempty"`
	Key        string `json:"key"`
	Salt       string `json:"salt,omitempty"`
	Iterations int    `json:"iterations,omitempty"`
}

type Password RawPassword

func (p Password) Match(pw string) (bool, error) {
	switch p.Type {
	case "":
		return p.Key == pw, nil
	case "pbkdf2":
		key, err := hex.DecodeString(p.Key)
		if err != nil {
			return false, err
		}
		salt, err := hex.DecodeString(p.Salt)
		if err != nil {
			return false, err
		}
		var h func() hash.Hash
		switch p.Hash {
		case "sha-256":
			h = sha256.New
		default:
			return false, errors.New("unknown hash type")
		}
		theirKey := pbkdf2.Key(
			[]byte(pw), salt, p.Iterations, len(key), h,
		)
		return bytes.Compare(key, theirKey) == 0, nil
	default:
		return false, errors.New("unknown password type")
	}
}

func (p *Password) UnmarshalJSON(b []byte) error {
	var k string
	err := json.Unmarshal(b, &k)
	if err == nil {
		*p = Password{
			Key: k,
		}
		return nil
	}
	var r RawPassword
	err = json.Unmarshal(b, &r)
	if err == nil {
		*p = Password(r)
	}
	return err
}

func (p Password) MarshalJSON() ([]byte, error) {
	if p.Type == "" && p.Hash == "" && p.Salt == "" && p.Iterations == 0 {
		return json.Marshal(p.Key)
	}
	return json.Marshal(RawPassword(p))
}

type ClientCredentials struct {
	Username string    `json:"username,omitempty"`
	Password *Password `json:"password,omitempty"`
}

type ClientPermissions struct {
	Op      bool `json:"op,omitempty"`
	Present bool `json:"present,omitempty"`
	Record  bool `json:"record,omitempty"`
}

type Challengeable interface {
	Username() string
	Challenge(string, ClientCredentials) bool
}

type Client interface {
	Group() *Group
	Id() string
	Challengeable
	SetPermissions(ClientPermissions)
	OverridePermissions(*Group) bool
	PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, label string) error
	PushClient(id, username string, add bool) error
}

type Kickable interface {
	Kick(id, user, message string) error
}
