// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/go-crypto/openpgp"
	"github.com/keybase/go-crypto/openpgp/armor"
	"github.com/keybase/go-crypto/openpgp/clearsign"
	"github.com/keybase/go-crypto/openpgp/packet"
)

type PGPVerifyArg struct {
	Source    io.Reader
	Signature []byte
	SignedBy  string
}

// PGPVerify is an engine.
type PGPVerify struct {
	arg        *PGPVerifyArg
	source     io.Reader
	signStatus *libkb.SignatureStatus
	signer     *libkb.User
	libkb.Contextified
}

// NewPGPVerify creates a PGPVerify engine.
func NewPGPVerify(g *libkb.GlobalContext, arg *PGPVerifyArg) *PGPVerify {
	return &PGPVerify{
		arg:          arg,
		Contextified: libkb.NewContextified(g),
	}
}

// Name is the unique engine name.
func (e *PGPVerify) Name() string {
	return "PGPVerify"
}

// GetPrereqs returns the engine prereqs.
func (e *PGPVerify) Prereqs() Prereqs {
	return Prereqs{}
}

// RequiredUIs returns the required UIs.
func (e *PGPVerify) RequiredUIs() []libkb.UIKind {
	return []libkb.UIKind{libkb.PgpUIKind}
}

// SubConsumers returns the other UI consumers for this engine.
func (e *PGPVerify) SubConsumers() []libkb.UIConsumer {
	return []libkb.UIConsumer{
		&PGPDecrypt{},
		&ScanKeys{},
		&ResolveThenIdentify2{},
	}
}

// Run starts the engine.
func (e *PGPVerify) Run(m libkb.MetaContext) error {
	var err error
	defer m.Trace("PGPVerify#Run", &err)()
	var sc libkb.StreamClassification
	sc, e.source, err = libkb.ClassifyStream(e.arg.Source)

	// For a Detached signature, we'll be expecting an UnknownStreamError
	if err != nil {
		if _, ok := err.(libkb.UnknownStreamError); !ok || len(e.arg.Signature) == 0 {
			return err
		}
	}

	if sc.Format == libkb.CryptoMessageFormatPGP && sc.Type == libkb.CryptoMessageTypeClearSignature {
		err = e.runClearsign(m)
		return err
	}
	if len(e.arg.Signature) == 0 {
		err = e.runAttached(m)
		return err
	}
	err = e.runDetached(m)
	return err
}

func (e *PGPVerify) SignatureStatus() *libkb.SignatureStatus {
	return e.signStatus
}

func (e *PGPVerify) Signer() *libkb.User {
	return e.signer
}

// runAttached verifies an attached signature
func (e *PGPVerify) runAttached(m libkb.MetaContext) error {
	arg := &PGPDecryptArg{
		Source:       e.source,
		Sink:         libkb.NopWriteCloser{W: ioutil.Discard},
		AssertSigned: true,
		SignedBy:     e.arg.SignedBy,
	}
	eng := NewPGPDecrypt(m.G(), arg)
	if err := RunEngine2(m, eng); err != nil {
		return err
	}
	e.signStatus = eng.SignatureStatus()
	e.signer = eng.Signer()

	return nil
}

// runDetached verifies a detached signature
func (e *PGPVerify) runDetached(m libkb.MetaContext) error {
	sk, err := NewScanKeys(m)
	if err != nil {
		return err
	}
	checkfn := openpgp.CheckDetachedSignature
	if libkb.IsArmored(e.arg.Signature) {
		checkfn = openpgp.CheckArmoredDetachedSignature
	}
	signer, err := checkfn(sk, e.source, bytes.NewReader(e.arg.Signature))
	if err != nil {
		return err
	}
	hashMethod, _, err := libkb.ExtractPGPSignatureHashMethod(sk, e.arg.Signature)
	if err != nil {
		return err
	}

	e.signer = sk.KeyOwnerByEntity(signer)
	e.signStatus = &libkb.SignatureStatus{IsSigned: true}

	if !libkb.IsHashSecure(hashMethod) {
		e.signStatus.Warnings = append(
			e.signStatus.Warnings,
			libkb.NewHashSecurityWarning(
				libkb.HashSecurityWarningSignatureHash,
				hashMethod,
				nil,
			),
		)
	}

	if signer != nil {
		if len(signer.UnverifiedRevocations) > 0 {
			return libkb.BadSigError{
				E: fmt.Sprintf("Key %x belonging to %q has been revoked by its designated revoker.", signer.PrimaryKey.KeyId, e.signer.GetName()),
			}
		}

		e.signStatus.Verified = true
		e.signStatus.Entity = signer
		if err := e.checkSignedBy(m); err != nil {
			return err
		}

		var r io.Reader = bytes.NewReader(e.arg.Signature)
		if libkb.IsArmored(e.arg.Signature) {
			block, err := armor.Decode(r)
			if err != nil {
				return err
			}
			r = block.Body
		}

		p, err := packet.Read(r)
		if err != nil {
			return err
		}

		if val, ok := p.(*packet.Signature); ok {
			e.signStatus.SignatureTime = val.CreationTime
		} else if val, ok := p.(*packet.SignatureV3); ok {
			e.signStatus.SignatureTime = val.CreationTime
		}

		if warnings := libkb.NewPGPKeyBundle(signer).SecurityWarnings(
			libkb.HashSecurityWarningSignersIdentityHash,
		); len(warnings) > 0 {
			e.signStatus.Warnings = append(
				e.signStatus.Warnings,
				warnings...,
			)
		}

		fingerprint := libkb.PGPFingerprint(signer.PrimaryKey.Fingerprint)
		err = OutputSignatureSuccess(m, fingerprint, e.signer, e.signStatus.SignatureTime, e.signStatus.Warnings)
		if err != nil {
			return err
		}
	}

	return nil
}

// runClearsign verifies a clearsign signature
func (e *PGPVerify) runClearsign(m libkb.MetaContext) error {
	// clearsign decode only works with the whole data slice, not a reader
	// so have to read it all here:
	msg, err := ioutil.ReadAll(e.source)
	if err != nil {
		return err
	}
	b, _ := clearsign.Decode(msg)
	if b == nil {
		return errors.New("Unable to decode clearsigned message")
	}

	sigBody, err := ioutil.ReadAll(b.ArmoredSignature.Body)
	if err != nil {
		return err
	}

	sk, err := NewScanKeys(m)
	if err != nil {
		return err
	}

	signer, err := openpgp.CheckDetachedSignature(sk, bytes.NewReader(b.Bytes), bytes.NewReader(sigBody))
	if err != nil {
		return fmt.Errorf("Check sig error: %s", err)
	}
	hashMethod, _, err := libkb.ExtractPGPSignatureHashMethod(sk, sigBody)
	if err != nil {
		return err
	}

	e.signer = sk.KeyOwnerByEntity(signer)
	e.signStatus = &libkb.SignatureStatus{IsSigned: true}

	if !libkb.IsHashSecure(hashMethod) {
		e.signStatus.Warnings = append(
			e.signStatus.Warnings,
			libkb.NewHashSecurityWarning(
				libkb.HashSecurityWarningSignatureHash,
				hashMethod,
				nil,
			),
		)
	}

	if signer != nil {
		if len(signer.UnverifiedRevocations) > 0 {
			return libkb.BadSigError{
				E: fmt.Sprintf("Key %x belonging to %q has been revoked by its designated revoker.", signer.PrimaryKey.KeyId, e.signer.GetName()),
			}
		}

		e.signStatus.Verified = true
		e.signStatus.Entity = signer
		if err := e.checkSignedBy(m); err != nil {
			return err
		}

		p, err := packet.Read(bytes.NewReader(sigBody))
		if err != nil {
			return err
		}

		if val, ok := p.(*packet.Signature); ok {
			e.signStatus.SignatureTime = val.CreationTime
		} else if val, ok := p.(*packet.SignatureV3); ok {
			e.signStatus.SignatureTime = val.CreationTime
		}

		if warnings := libkb.NewPGPKeyBundle(signer).SecurityWarnings(
			libkb.HashSecurityWarningSignersIdentityHash,
		); len(warnings) > 0 {
			e.signStatus.Warnings = append(
				e.signStatus.Warnings,
				warnings...,
			)
		}

		fingerprint := libkb.PGPFingerprint(signer.PrimaryKey.Fingerprint)
		err = OutputSignatureSuccess(m, fingerprint, e.signer, e.signStatus.SignatureTime, e.signStatus.Warnings)
		if err != nil {
			return err
		}
	}

	return nil
}

func (e *PGPVerify) checkSignedBy(m libkb.MetaContext) error {
	if len(e.arg.SignedBy) == 0 {
		// no assertion necessary
		return nil
	}
	if !e.signStatus.Verified || e.signStatus.Entity == nil || e.signer == nil {
		// signature not valid, so no need to assert
		return nil
	}

	// have: a valid signature, the signature's owner, and a user assertion to
	// match against
	m.Debug("checking signed by assertion: %q", e.arg.SignedBy)

	// load the user in SignedBy
	arg := keybase1.Identify2Arg{
		UserAssertion: e.arg.SignedBy,
		AlwaysBlock:   true,
		NeedProofSet:  true,
		NoSkipSelf:    true,
	}
	eng := NewResolveThenIdentify2(e.G(), &arg)
	if err := RunEngine2(m, eng); err != nil {
		return err
	}
	res, err := eng.Result(m)
	if err != nil {
		return err
	}
	signByUser := res.Upk

	// check if it is equal to signature owner
	if !e.signer.GetUID().Equal(signByUser.GetUID()) {
		return libkb.BadSigError{
			E: fmt.Sprintf("Signer %q did not match signed by assertion %q", e.signer.GetName(), e.arg.SignedBy),
		}
	}
	return nil
}
