package pngmeta

import (
	"bytes"
	"gf-lt/models"
	"encoding/base64"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"os"
)

const (
	pngHeader     = "\x89PNG\r\n\x1a\n"
	textChunkType = "tEXt"
)

// WriteToPng embeds the metadata into the specified PNG file and writes the result to outfile.
func WriteToPng(metadata *models.CharCardSpec, sourcePath, outfile string) error {
	pngData, err := os.ReadFile(sourcePath)
	if err != nil {
		return err
	}
	jsonData, err := json.Marshal(metadata)
	if err != nil {
		return err
	}
	base64Data := base64.StdEncoding.EncodeToString(jsonData)
	embedData := PngEmbed{
		Key:   "gf-lt", // Replace with appropriate key constant
		Value: base64Data,
	}
	var outputBuffer bytes.Buffer
	if _, err := outputBuffer.Write([]byte(pngHeader)); err != nil {
		return err
	}
	chunks, iend, err := processChunks(pngData[8:])
	if err != nil {
		return err
	}
	for _, chunk := range chunks {
		outputBuffer.Write(chunk)
	}
	newChunk, err := createTextChunk(embedData)
	if err != nil {
		return err
	}
	outputBuffer.Write(newChunk)
	outputBuffer.Write(iend)
	return os.WriteFile(outfile, outputBuffer.Bytes(), 0666)
}

// processChunks extracts non-tEXt chunks and locates the IEND chunk
func processChunks(data []byte) ([][]byte, []byte, error) {
	var (
		chunks    [][]byte
		iendChunk []byte
		reader    = bytes.NewReader(data)
	)
	for {
		var chunkLength uint32
		if err := binary.Read(reader, binary.BigEndian, &chunkLength); err != nil {
			if errors.Is(err, io.EOF) {
				break
			}
			return nil, nil, fmt.Errorf("error reading chunk length: %w", err)
		}
		chunkType := make([]byte, 4)
		if _, err := reader.Read(chunkType); err != nil {
			return nil, nil, fmt.Errorf("error reading chunk type: %w", err)
		}
		chunkData := make([]byte, chunkLength)
		if _, err := reader.Read(chunkData); err != nil {
			return nil, nil, fmt.Errorf("error reading chunk data: %w", err)
		}
		crc := make([]byte, 4)
		if _, err := reader.Read(crc); err != nil {
			return nil, nil, fmt.Errorf("error reading CRC: %w", err)
		}
		fullChunk := bytes.NewBuffer(nil)
		if err := binary.Write(fullChunk, binary.BigEndian, chunkLength); err != nil {
			return nil, nil, fmt.Errorf("error writing chunk length: %w", err)
		}
		if _, err := fullChunk.Write(chunkType); err != nil {
			return nil, nil, fmt.Errorf("error writing chunk type: %w", err)
		}
		if _, err := fullChunk.Write(chunkData); err != nil {
			return nil, nil, fmt.Errorf("error writing chunk data: %w", err)
		}
		if _, err := fullChunk.Write(crc); err != nil {
			return nil, nil, fmt.Errorf("error writing CRC: %w", err)
		}
		switch string(chunkType) {
		case "IEND":
			iendChunk = fullChunk.Bytes()
			return chunks, iendChunk, nil
		case textChunkType:
			continue // Skip existing tEXt chunks
		default:
			chunks = append(chunks, fullChunk.Bytes())
		}
	}
	return nil, nil, errors.New("IEND chunk not found")
}

// createTextChunk generates a valid tEXt chunk with proper CRC
func createTextChunk(embed PngEmbed) ([]byte, error) {
	content := bytes.NewBuffer(nil)
	content.WriteString(embed.Key)
	content.WriteByte(0) // Null separator
	content.WriteString(embed.Value)
	data := content.Bytes()
	crc := crc32.NewIEEE()
	crc.Write([]byte(textChunkType))
	crc.Write(data)
	chunk := bytes.NewBuffer(nil)
	if err := binary.Write(chunk, binary.BigEndian, uint32(len(data))); err != nil {
		return nil, fmt.Errorf("error writing chunk length: %w", err)
	}
	if _, err := chunk.WriteString(textChunkType); err != nil {
		return nil, fmt.Errorf("error writing chunk type: %w", err)
	}
	if _, err := chunk.Write(data); err != nil {
		return nil, fmt.Errorf("error writing chunk data: %w", err)
	}
	if err := binary.Write(chunk, binary.BigEndian, crc.Sum32()); err != nil {
		return nil, fmt.Errorf("error writing CRC: %w", err)
	}
	return chunk.Bytes(), nil
}
