// Package identity integrates the existing identicon-cli program without
// copying its GUI or reimplementing its rendering algorithm.
package identity

import (
	"bytes"
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"image"
	"image/png"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

var cliCandidates = []string{
	"/home/gabriel1/Projects/identicons/identicons-cli",
	"/home/gabriel1/Workspace/Projects/identicons/identicons",
	"/usr/local/bin/identicon-cli",
	"/usr/bin/identicon-cli",
	"/SDCard/identicon-cli",
	"/Shared/identicon-cli",
}

// FindCLI returns the configured or locally installed identicon-cli binary.
func FindCLI() (string, error) {
	if configured := strings.TrimSpace(os.Getenv("AEGIS_IDENTICON_CLI")); configured != "" {
		if isExecutable(configured) {
			return configured, nil
		}
		return "", fmt.Errorf("AEGIS_IDENTICON_CLI is not executable: %s", configured)
	}
	for _, candidate := range cliCandidates {
		if isExecutable(candidate) {
			return candidate, nil
		}
	}
	return "", errors.New("identicon-cli executable not found")
}

func isExecutable(path string) bool {
	info, err := os.Stat(path)
	return err == nil && info.Mode().IsRegular() && info.Mode()&0o111 != 0
}

// GenerateFace delegates to identicon-cli with its existing 48px rendering
// and returns the base64 PNG payload used in a Face header.
func GenerateFace(seed string) (string, error) {
	if strings.TrimSpace(seed) == "" {
		return "", errors.New("identicon seed is required")
	}
	return generateFace(seed, 48)
}

func generateFace(seed string, size int) (string, error) {
	if strings.TrimSpace(seed) == "" {
		return "", errors.New("identicon seed is required")
	}
	if size <= 0 {
		return "", errors.New("identicon size must be positive")
	}
	executable, err := FindCLI()
	if err != nil {
		return "", err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	command := exec.CommandContext(ctx, executable, "-input", seed, "-format", "base64", "-size", fmt.Sprintf("%d", size), "-transparent")
	var stdout, stderr bytes.Buffer
	command.Stdout = &stdout
	command.Stderr = &stderr
	if err := command.Run(); err != nil {
		return "", fmt.Errorf("run identicon-cli: %w: %s", err, strings.TrimSpace(stderr.String()))
	}
	value := strings.TrimSpace(stdout.String())
	if _, err := pngImageFromBase64(value, size); err != nil {
		return "", fmt.Errorf("identicon-cli returned invalid Face data: %w", err)
	}
	return value, nil
}

func decodeFacePNG(value string) ([]byte, error) {
	return pngBytes(value, 48)
}

func pngBytes(value string, size int) ([]byte, error) {
	data, err := base64.StdEncoding.DecodeString(value)
	if err != nil {
		return nil, fmt.Errorf("decode base64: %w", err)
	}
	if _, err := pngImage(data, size); err != nil {
		return nil, err
	}
	return data, nil
}

func pngImageFromBase64(value string, size int) (image.Image, error) {
	data, err := pngBytes(value, size)
	if err != nil {
		return nil, err
	}
	return pngImage(data, size)
}

func pngImage(data []byte, size int) (image.Image, error) {
	img, err := png.Decode(bytes.NewReader(data))
	if err != nil {
		return nil, fmt.Errorf("decode PNG: %w", err)
	}
	if img.Bounds().Dx() != size || img.Bounds().Dy() != size {
		return nil, fmt.Errorf("PNG dimensions are %dx%d, want %dx%d", img.Bounds().Dx(), img.Bounds().Dy(), size, size)
	}
	return img, nil
}

// FormatFaceHeader folds the Face value in the same 70/75-byte pattern used
// by the existing identicon-cli output files.
func FormatFaceHeader(value string) string {
	if len(value) <= 70 {
		return "Face: " + value
	}
	var out strings.Builder
	out.WriteString("Face: ")
	out.WriteString(value[:70])
	for position := 70; position < len(value); position += 75 {
		end := position + 75
		if end > len(value) {
			end = len(value)
		}
		out.WriteString("\r\n ")
		out.WriteString(value[position:end])
	}
	return out.String()
}

// CLIPathForDisplay returns a stable path label without exposing environment
// secrets or invoking a shell.
func CLIPathForDisplay(path string) string {
	if path == "" {
		return ""
	}
	return filepath.Clean(path)
}
