package pgp

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/textproto"
	"strings"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/ProtonMail/go-crypto/openpgp/armor"
)

// Entity is a MIME entity: its own part-level headers plus its body — deliberately
// the same shape as smime.Entity, so webui's compose/read handlers can pass the same
// value between either package's Encrypt/Decrypt without conversion glue.
type Entity struct {
	Headers []string
	Body    []byte
}

func (e Entity) bytes() []byte {
	var buf bytes.Buffer
	for _, h := range e.Headers {
		buf.WriteString(h)
		buf.WriteString("\r\n")
	}
	buf.WriteString("\r\n")
	buf.Write(e.Body)
	return buf.Bytes()
}

// parseEntity splits raw bytes (headers, a blank line, then body) back into an
// Entity — used to recover the inner MIME entity after Decrypt.
func parseEntity(raw []byte) Entity {
	idx := bytes.Index(raw, []byte("\r\n\r\n"))
	sep := 4
	if idx < 0 {
		idx = bytes.Index(raw, []byte("\n\n"))
		sep = 2
	}
	if idx < 0 {
		return Entity{Body: raw}
	}
	var headers []string
	for _, line := range strings.Split(string(raw[:idx]), "\n") {
		line = strings.TrimRight(line, "\r")
		if line == "" {
			continue
		}
		headers = append(headers, line)
	}
	return Entity{Headers: headers, Body: raw[idx+sep:]}
}

func headerValue(headers []string, name string) string {
	for _, h := range headers {
		if i := strings.Index(h, ":"); i >= 0 && strings.EqualFold(strings.TrimSpace(h[:i]), name) {
			return strings.TrimSpace(h[i+1:])
		}
	}
	return ""
}

// EncryptEntity wraps entity's bytes as an RFC 3156 PGP/MIME multipart/encrypted
// structure, encrypted to recipients. Pass every recipient's public key, including
// the sender's own, so a copy kept in Sent stays readable — mirrors
// smime.Encrypt's same convention.
func EncryptEntity(entity Entity, recipients []*openpgp.Entity) (Entity, error) {
	if len(recipients) == 0 {
		return Entity{}, errors.New("pgp: no recipient keys provided")
	}

	var armored bytes.Buffer
	aw, err := armor.Encode(&armored, "PGP MESSAGE", nil)
	if err != nil {
		return Entity{}, fmt.Errorf("pgp: encrypt: %w", err)
	}
	pt, err := openpgp.Encrypt(aw, recipients, nil, nil, defaultConfig())
	if err != nil {
		return Entity{}, fmt.Errorf("pgp: encrypt: %w", err)
	}
	if _, err := pt.Write(entity.bytes()); err != nil {
		return Entity{}, fmt.Errorf("pgp: encrypt: %w", err)
	}
	if err := pt.Close(); err != nil {
		return Entity{}, fmt.Errorf("pgp: encrypt: %w", err)
	}
	if err := aw.Close(); err != nil {
		return Entity{}, fmt.Errorf("pgp: encrypt: %w", err)
	}

	var body bytes.Buffer
	mw := multipart.NewWriter(&body)
	ctrlPart, err := mw.CreatePart(textproto.MIMEHeader{
		"Content-Type":              {"application/pgp-encrypted"},
		"Content-Transfer-Encoding": {"7bit"},
	})
	if err != nil {
		return Entity{}, err
	}
	if _, err := ctrlPart.Write([]byte("Version: 1\r\n")); err != nil {
		return Entity{}, err
	}
	dataPart, err := mw.CreatePart(textproto.MIMEHeader{
		"Content-Type":              {`application/octet-stream; name="encrypted.asc"`},
		"Content-Disposition":       {`inline; filename="encrypted.asc"`},
		"Content-Transfer-Encoding": {"7bit"},
	})
	if err != nil {
		return Entity{}, err
	}
	if _, err := dataPart.Write(armored.Bytes()); err != nil {
		return Entity{}, err
	}
	if err := mw.Close(); err != nil {
		return Entity{}, err
	}

	return Entity{
		Headers: []string{
			fmt.Sprintf(`Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="%s"`, mw.Boundary()),
		},
		Body: body.Bytes(),
	}, nil
}

// DecryptEntity reverses EncryptEntity, decrypting with unlockedIdentity (its
// private key material must already be unlocked via UnlockPrivateKey — this
// function never takes a passphrase itself).
func DecryptEntity(entity Entity, unlockedIdentity *openpgp.Entity) (Entity, error) {
	ct := headerValue(entity.Headers, "Content-Type")
	mediaType, params, err := mime.ParseMediaType(ct)
	if err != nil || mediaType != "multipart/encrypted" {
		return Entity{}, errors.New("pgp: not a multipart/encrypted message")
	}
	if !strings.EqualFold(params["protocol"], "application/pgp-encrypted") {
		return Entity{}, fmt.Errorf("pgp: unsupported multipart/encrypted protocol %q", params["protocol"])
	}
	boundary := params["boundary"]
	if boundary == "" {
		return Entity{}, errors.New("pgp: missing multipart boundary")
	}

	mr := multipart.NewReader(bytes.NewReader(entity.Body), boundary)
	// First part is the application/pgp-encrypted control part ("Version: 1") — not
	// needed, the actual ciphertext is the second part.
	if _, err := mr.NextPart(); err != nil {
		return Entity{}, fmt.Errorf("pgp: read control part: %w", err)
	}
	dataPart, err := mr.NextPart()
	if err != nil {
		return Entity{}, fmt.Errorf("pgp: read data part: %w", err)
	}
	armoredCiphertext, err := io.ReadAll(dataPart)
	if err != nil {
		return Entity{}, err
	}

	block, err := armor.Decode(bytes.NewReader(armoredCiphertext))
	if err != nil {
		return Entity{}, fmt.Errorf("pgp: invalid armored ciphertext: %w", err)
	}
	md, err := openpgp.ReadMessage(block.Body, openpgp.EntityList{unlockedIdentity}, nil, defaultConfig())
	if err != nil {
		return Entity{}, fmt.Errorf("pgp: decrypt: %w", err)
	}
	plaintext, err := io.ReadAll(md.UnverifiedBody)
	if err != nil {
		return Entity{}, fmt.Errorf("pgp: decrypt: %w", err)
	}
	return parseEntity(plaintext), nil
}
