// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.

// TODO: Add more logging
package main

import (
	"bytes"
	"fmt"
	"log/slog"
	"strings"
	"time"

	"github.com/ProtonMail/gopenpgp/v3/crypto" // MIT License
	"github.com/beevik/etree"                  // BSD-2-clause
	"github.com/xmppo/go-xmpp"                 // BSD-3-Clause
)

func legacyPGPEncryptFile(oxPrivKey *crypto.Key, keyRing *crypto.KeyRing, file *bytes.Buffer) (encryptedFileBuffer *bytes.Buffer, err error) {
	pgpEncrypt, err := crypto.PGP().Encryption().
		Recipients(keyRing).
		SigningKey(oxPrivKey).New()
	if err != nil {
		return encryptedFileBuffer, err
	}
	encryptedFile, err := pgpEncrypt.Encrypt(file.Bytes())
	if err != nil {
		return encryptedFileBuffer, err
	}
	encryptedFileArmored, err := encryptedFile.ArmorBytes()
	if err != nil {
		return encryptedFileBuffer, err
	}
	encryptedFileBuffer = bytes.NewBuffer(encryptedFileArmored)
	return encryptedFileBuffer, err
}

func legacyPGPEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, keyRing crypto.KeyRing, message string) (string, error) {
	const statusOnline = "<status>Online</status>"
	if message == "" {
		return "", nil
	}
	ownJid := strings.Split(client.JID(), "/")[0]
	if recipient != ownJid {
		opk, err := oxPrivKey.GetPublicKey()
		if err == nil {
			ownKey, _ := crypto.NewKey(opk)
			_ = keyRing.AddKey(ownKey)
		}
	}
	id := getID()
	legacyPGP := etree.NewDocument()
	legacyPGP.WriteSettings.AttrSingleQuote = true
	legacyPGPMsg := legacyPGP.CreateElement("message")
	legacyPGPMsg.CreateAttr("id", id)
	legacyPGPMsg.CreateAttr("to", recipient)
	legacyPGPMsgBody := legacyPGPMsg.CreateElement("body")
	legacyPGPMsgBody.CreateText("This message is encrypted using XEP-0027 PGP encryption.")
	legacyPGPMsgX := legacyPGPMsg.CreateElement("x")
	legacyPGPMsgX.CreateAttr("xmlns", nsJabberEncrypted)
	legacyPGPMsgEnc := legacyPGPMsg.CreateElement("encryption")
	legacyPGPMsgEnc.CreateAttr("namespace", nsJabberEncrypted)
	legacyPGPMsgEnc.CreateAttr("xmlns", nsEme)
	legacyPGPMsgOrigID := legacyPGPMsg.CreateElement("origin-id")
	legacyPGPMsgOrigID.CreateAttr("xmlns", nsSid)
	legacyPGPMsgOrigID.CreateAttr("id", id)
	pgpEncrypt, err := crypto.PGP().Encryption().
		Recipients(&keyRing).
		SigningKey(oxPrivKey).New()
	if err != nil {
		return strError, fmt.Errorf("legacyPGPEncrypt: failed to create pgp encryption interface: %w", err)
	}
	pgpMessage, err := pgpEncrypt.Encrypt([]byte(message))
	if err != nil {
		return strError, fmt.Errorf("legacyPGPEncrypt: failed to create pgp message: %w", err)
	}
	pgpMessageArmored, err := pgpMessage.Armor()
	pgpMessageArmored = strings.TrimPrefix(pgpMessageArmored, legacyPGPMsgBegin)
	pgpMessageArmored = strings.TrimSuffix(pgpMessageArmored, legacyPGPMsgEnd)
	if err != nil {
		return strError, fmt.Errorf("legacyPGPEncrypt: failed to create pgp message: %w", err)
	}
	legacyPGPMsgX.CreateText(pgpMessageArmored)
	lpm, err := legacyPGP.WriteToString()
	if err != nil {
		return strError, fmt.Errorf("legacyPGPEncrypt: failed to create xml for ox message: %w", err)
	}
	signer, err := crypto.PGP().Sign().SigningKey(oxPrivKey).Detached().New()
	if err != nil {
		return strError, err
	}
	signedStatusWithHeader, err := signer.Sign([]byte(statusOnline), crypto.Armor)
	if err != nil {
		return strError, err
	}
	signedStatus := strings.TrimPrefix(string(signedStatusWithHeader), legacyPGPSigBegin)
	signedStatus = strings.TrimSuffix(signedStatus, legacyPGPSigEnd)
	signedPresence := fmt.Sprintf("<presence to='%s' from='%s'>%s<x xmlns='%s'>%s</x></presence>",
		recipient, ownJid, statusOnline, nsSigned, signedStatus)
	_, err = client.SendOrg(signedPresence)
	if err != nil {
		return strError, err
	}

	return lpm, nil
}

func isLegacyPGPMsg(m xmpp.Chat) bool {
	slog.Info("legacy pgp: checking pgp message")
	for _, r := range m.OtherElem {
		slog.Info("legacy pgp: checking for pgp message: comparing", "XMLNameSpace", r.XMLName.Space,
			"LegacyPGPNameSpace", nsJabberEncrypted)
		if r.XMLName.Space == nsJabberEncrypted {
			slog.Info("legacy pgp: checking for pgp message:", "result", true)
			return true
		}
	}
	slog.Info("legacy pgp: checking for pgp message:", "result", false)
	return false
}

func legacyPGPDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string, oxPrivKey *crypto.Key) (string, time.Time, error) {
	var legacyPGPMsg *crypto.PGPMessage
	var err error
	sender := strings.Split(m.Remote, "/")[0]
	for _, r := range m.OtherElem {
		if r.XMLName.Space == nsJabberEncrypted {
			armored := legacyPGPMsgBegin +
				r.InnerXML + legacyPGPMsgEnd
			legacyPGPMsg, err = crypto.NewPGPMessageFromArmored(armored)
			if err != nil {
				return strError, time.Now(), err
			}
			break
		}
	}
	if legacyPGPMsg == nil {
		return strError, time.Now(), fmt.Errorf("legacypgp: could net unarmor pgp message")
	}
	keyRing, err := crypto.NewKeyRing(oxPrivKey)
	if err != nil {
		return strError, time.Now(), err
	}
	slog.Info("legacy pgp: decrypting: getting senders key", "sender", sender)
	senderKeyRing, err := oxGetPublicKeyRing(client, iqc, sender)
	if err != nil {
		return strError, time.Now(), err
	}
	slog.Info("legacy pgp: decrypting message")
	pgpDecrypt, err := crypto.PGP().Decryption().
		DecryptionKeys(keyRing).
		VerificationKeys(senderKeyRing).
		New()
	if err != nil {
		return strError, time.Now(), err
	}
	decryptMsg, err := pgpDecrypt.Decrypt(legacyPGPMsg.Bytes(), crypto.Bytes)
	if err != nil {
		return strError, time.Now(), err
	}
	// Remove invalid code points.
	slog.Info("legacy pgp: removing invalid code points from decrypted message")
	message := validUTF8(decryptMsg.String())
	return message, time.Now(), nil
}
