package helper

import (
	"bytes"
	"crypto/sha256"
	"errors"
	"io"
	"testing"

	"github.com/ProtonMail/gopenpgp/v2/constants"
	"github.com/ProtonMail/gopenpgp/v2/crypto"
)

func cloneTestData() (a, b []byte) {
	a = []byte("Hello World!")
	b = clone(a)
	return a, b
}
func Test_clone(t *testing.T) {
	if a, b := cloneTestData(); !bytes.Equal(a, b) {
		t.Fatalf("expected %x, got %x", a, b)
	}
}

func TestMobile2GoWriter(t *testing.T) {
	testData := []byte("Hello World!")
	outBuf := &bytes.Buffer{}
	reader := bytes.NewReader(testData)
	writer := NewMobile2GoWriter(outBuf)
	bufSize := 2
	writeBuf := make([]byte, bufSize)
	reachedEnd := false
	for !reachedEnd {
		n, err := reader.Read(writeBuf)
		if err != nil {
			if errors.Is(err, io.EOF) {
				reachedEnd = true
			} else {
				t.Fatal("Expected no error while reading, got:", err)
			}
		}
		writtenTotal := 0
		for writtenTotal < n {
			written, err := writer.Write(writeBuf[writtenTotal:n])
			if err != nil {
				t.Fatal("Expected no error while writing, got:", err)
			}
			writtenTotal += written
		}
	}
	if writtenData := outBuf.Bytes(); !bytes.Equal(testData, writtenData) {
		t.Fatalf("expected %x, got %x", testData, writtenData)
	}
}

func TestMobile2GoWriterWithSHA256(t *testing.T) {
	testData := []byte("Hello World!")
	testHash := sha256.Sum256(testData)
	outBuf := &bytes.Buffer{}
	reader := bytes.NewReader(testData)
	writer := NewMobile2GoWriterWithSHA256(outBuf)
	bufSize := 2
	writeBuf := make([]byte, bufSize)
	reachedEnd := false
	for !reachedEnd {
		n, err := reader.Read(writeBuf)
		if err != nil {
			if errors.Is(err, io.EOF) {
				reachedEnd = true
			} else {
				t.Fatal("Expected no error while reading, got:", err)
			}
		}
		writtenTotal := 0
		for writtenTotal < n {
			written, err := writer.Write(writeBuf[writtenTotal:n])
			if err != nil {
				t.Fatal("Expected no error while writing, got:", err)
			}
			writtenTotal += written
		}
	}
	if writtenData := outBuf.Bytes(); !bytes.Equal(testData, writtenData) {
		t.Fatalf("expected data to be %x, got %x", testData, writtenData)
	}

	if writtenHash := writer.GetSHA256(); !bytes.Equal(testHash[:], writtenHash) {
		t.Fatalf("expected has to be %x, got %x", testHash, writtenHash)
	}
}

func TestGo2AndroidReader(t *testing.T) {
	testData := []byte("Hello World!")
	reader := NewGo2AndroidReader(bytes.NewReader(testData))
	var readData []byte
	bufSize := 2
	buffer := make([]byte, bufSize)
	reachedEnd := false
	for !reachedEnd {
		n, err := reader.Read(buffer)
		if err != nil {
			t.Fatal("Expected no error while reading, got:", err)
		}
		reachedEnd = n < 0
		if n > 0 {
			readData = append(readData, buffer[:n]...)
		}
	}
	if !bytes.Equal(testData, readData) {
		t.Fatalf("expected data to be %x, got %x", testData, readData)
	}
}

func TestGo2IOSReader(t *testing.T) {
	testData := []byte("Hello World!")
	reader := NewGo2IOSReader(bytes.NewReader(testData))
	var readData []byte
	bufSize := 2
	reachedEnd := false
	for !reachedEnd {
		res, err := reader.Read(bufSize)
		if err != nil {
			t.Fatal("Expected no error while reading, got:", err)
		}
		n := res.N
		reachedEnd = res.IsEOF
		if n > 0 {
			readData = append(readData, res.Data[:n]...)
		}
	}
	if !bytes.Equal(testData, readData) {
		t.Fatalf("expected data to be %x, got %x", testData, readData)
	}
}

type testMobileReader struct {
	reader      io.Reader
	returnError bool
}

func (r *testMobileReader) Read(max int) (*MobileReadResult, error) {
	if r.returnError {
		return nil, errors.New("gopenpgp: test - forced error while reading")
	}
	buf := make([]byte, max)
	n, err := r.reader.Read(buf)
	eof := false
	if err != nil {
		if errors.Is(err, io.EOF) {
			eof = true
		} else {
			return nil, errors.New("gopenpgp: test - error while reading")
		}
	}
	return NewMobileReadResult(n, eof, buf[:n]), nil
}

func TestMobile2GoReader(t *testing.T) {
	testData := []byte("Hello World!")
	reader := NewMobile2GoReader(&testMobileReader{bytes.NewReader(testData), false})
	var readData []byte
	bufSize := 2
	readBuf := make([]byte, bufSize)
	reachedEnd := false
	for !reachedEnd {
		n, err := reader.Read(readBuf)
		if err != nil {
			if errors.Is(err, io.EOF) {
				reachedEnd = true
			} else {
				t.Fatal("Expected no error while reading, got:", err)
			}
		}
		if n > 0 {
			readData = append(readData, readBuf[:n]...)
		}
	}
	if !bytes.Equal(testData, readData) {
		t.Fatalf("expected data to be %x, got %x", testData, readData)
	}
	readerErr := NewMobile2GoReader(&testMobileReader{bytes.NewReader(testData), true})
	if _, err := readerErr.Read(readBuf); err == nil {
		t.Fatal("expected an error while reading, got nil")
	}
}

func setUpTestKeyRing() (*crypto.KeyRing, *crypto.KeyRing, error) {
	testKey, err := crypto.GenerateKey("test", "test@protonmail.com", "x25519", 256)
	if err != nil {
		return nil, nil, err
	}
	testPublicKey, err := testKey.ToPublic()
	if err != nil {
		return nil, nil, err
	}
	testPrivateKeyRing, err := crypto.NewKeyRing(testKey)
	if err != nil {
		return nil, nil, err
	}
	testPublicKeyRing, err := crypto.NewKeyRing(testPublicKey)
	if err != nil {
		return nil, nil, err
	}
	return testPublicKeyRing, testPrivateKeyRing, nil
}

func TestExplicitVerifyAllGoesWell(t *testing.T) {
	data := []byte("hello")
	pubKR, privKR, err := setUpTestKeyRing()
	if err != nil {
		t.Fatalf("Got an error while loading test key: %v", err)
	}
	defer privKR.ClearPrivateParams()
	ciphertext, err := pubKR.Encrypt(crypto.NewPlainMessage(data), privKR)
	if err != nil {
		t.Fatalf("Got an error while encrypting test data: %v", err)
	}
	reader, err := privKR.DecryptStream(
		bytes.NewReader(ciphertext.Data),
		pubKR,
		crypto.GetUnixTime(),
	)
	if err != nil {
		t.Fatalf("Got an error while decrypting stream data: %v", err)
	}
	_, err = io.ReadAll(reader)
	if err != nil {
		t.Fatal("Expected no error while reading, got:", err)
	}
	sigErr, err := VerifySignatureExplicit(reader)
	if sigErr != nil {
		t.Fatalf("Got a signature error while verifying embedded sig: %v", sigErr)
	}
	if err != nil {
		t.Fatalf("Got an error while verifying embedded sig: %v", err)
	}
}

func TestExplicitVerifyTooEarly(t *testing.T) {
	data := []byte("hello")
	pubKR, privKR, err := setUpTestKeyRing()
	if err != nil {
		t.Fatalf("Got an error while loading test key: %v", err)
	}
	defer privKR.ClearPrivateParams()
	ciphertext, err := pubKR.Encrypt(crypto.NewPlainMessage(data), privKR)
	if err != nil {
		t.Fatalf("Got an error while encrypting test data: %v", err)
	}
	reader, err := privKR.DecryptStream(
		bytes.NewReader(ciphertext.Data),
		pubKR,
		crypto.GetUnixTime(),
	)
	if err != nil {
		t.Fatalf("Got an error while decrypting stream data: %v", err)
	}
	buff := make([]byte, 1)
	_, err = reader.Read(buff)
	if err != nil {
		t.Fatalf("Got an error while reading decrypted data: %v", err)
	}
	sigErr, err := VerifySignatureExplicit(reader)
	if sigErr != nil {
		t.Fatalf("Got a signature error while verifying embedded sig: %v", sigErr)
	}
	if err == nil {
		t.Fatalf("Got no error while verifying a reader before reading it entirely")
	}
}

func TestExplicitVerifyNoSig(t *testing.T) {
	data := []byte("hello")
	pubKR, privKR, err := setUpTestKeyRing()
	if err != nil {
		t.Fatalf("Got an error while loading test key: %v", err)
	}
	defer privKR.ClearPrivateParams()
	ciphertext, err := pubKR.Encrypt(crypto.NewPlainMessage(data), nil)
	if err != nil {
		t.Fatalf("Got an error while encrypting test data: %v", err)
	}
	reader, err := privKR.DecryptStream(
		bytes.NewReader(ciphertext.Data),
		pubKR,
		crypto.GetUnixTime(),
	)
	if err != nil {
		t.Fatalf("Got an error while decrypting stream data: %v", err)
	}
	_, err = io.ReadAll(reader)
	if err != nil {
		t.Fatal("Expected no error while reading, got:", err)
	}
	sigErr, err := VerifySignatureExplicit(reader)
	if sigErr == nil {
		t.Fatal("Got no signature error while verifying unsigned data")
	}
	if sigErr.Status != constants.SIGNATURE_NOT_SIGNED {
		t.Fatal("Signature error status was not SIGNATURE_NOT_SIGNED")
	}
	if err != nil {
		t.Fatalf("Got an error while verifying embedded sig: %v", err)
	}
}

func TestExplicitVerifyWrongVerifier(t *testing.T) {
	data := []byte("hello")
	pubKR, privKR, err := setUpTestKeyRing()
	if err != nil {
		t.Fatalf("Got an error while loading test key: %v", err)
	}
	defer privKR.ClearPrivateParams()
	_, privKR2, err := setUpTestKeyRing()
	if err != nil {
		t.Fatalf("Got an error while loading test key: %v", err)
	}
	defer privKR2.ClearPrivateParams()
	ciphertext, err := pubKR.Encrypt(crypto.NewPlainMessage(data), privKR2)
	if err != nil {
		t.Fatalf("Got an error while encrypting test data: %v", err)
	}
	reader, err := privKR.DecryptStream(
		bytes.NewReader(ciphertext.Data),
		pubKR,
		crypto.GetUnixTime(),
	)
	if err != nil {
		t.Fatalf("Got an error while decrypting stream data: %v", err)
	}
	_, err = io.ReadAll(reader)
	if err != nil {
		t.Fatal("Expected no error while reading, got:", err)
	}
	sigErr, err := VerifySignatureExplicit(reader)
	if sigErr == nil {
		t.Fatal("Got no signature error while verifying with wrong key")
	}
	if sigErr.Status != constants.SIGNATURE_NO_VERIFIER {
		t.Fatal("Signature error status was not SIGNATURE_NO_VERIFIER")
	}
	if err != nil {
		t.Fatalf("Got an error while verifying embedded sig: %v", err)
	}
}
