package encrypt

// Nový návrh: Password-based šifrované úložiště s derivací klíče (scrypt) +
// vytvoření uživatelských RSA klíčů při prvotní inicializaci. Private/Public
// (a volitelně self-signed cert) se uloží jako položky uvnitř zašifrovaného
// JSON. Soubor není možno otevřít bez hesla.
//
// Formát na disku (JSON):
// {
//   "v":1,
//   "kdf":"scrypt",
//   "N":32768, "r":8, "p":1,
//   "salt":"base64(...)",
//   "n":"base64(nonce)",
//   "c":"base64(ciphertext)"
// }
// Plaintext (po dešifrování):
// {
//   "updated":"RFC3339",
//   "data":{ key: <json>, ... }
// }
// Povinné interní klíče:
//   _identity_private_pem
//   _identity_public_pem
//   _identity_cert_pem   (volitelně)
//
// API: CreateEncryptedStore (nový), OpenEncryptedStore, ChangePassword.
// Původní NewSecureJSONStore (s KeyProvider) je odstraněn – přechod na heslo.

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"golang.org/x/crypto/scrypt"
)

// SecureJSONStore definuje metody úložiště.
type SecureJSONStore interface {
	Put(key string, value any) error
	Get(key string, dst any) error
	Delete(key string) error
	ListKeys() []string
	Flush() error
	Close() error
	ChangePassword(newPassword string) error
	Has(key string) bool
	// Identity getters (pem). Vrací prázdný string, pokud neexistují.
	IdentityPrivatePEM() string
	IdentityPublicPEM() string
	IdentityCertPEM() string
}

type internalPlain struct {
	Updated time.Time                  `json:"updated"`
	Data    map[string]json.RawMessage `json:"data"`
}

type fileEnvelope struct {
	Version int    `json:"v"`
	KDF     string `json:"kdf"`
	N       int    `json:"N"`
	R       int    `json:"r"`
	P       int    `json:"p"`
	Salt    string `json:"salt"`
	Nonce   string `json:"n"`
	Cipher  string `json:"c"`
}

type secureJSONStore struct {
	mu       sync.RWMutex
	path     string
	kdfN     int
	kdfR     int
	kdfP     int
	salt     []byte
	key      []byte // odvozený AES-256 klíč
	dirty    bool
	plain    internalPlain
	hasIdent bool
}

const (
	defaultKDFN = 32768
	defaultKDFR = 8
	defaultKDFP = 1
)

// CreateEncryptedStore vytvoří nový soubor; error pokud již existuje.
func CreateEncryptedStore(path, password string, generateIdentity bool, commonName string) (SecureJSONStore, error) {
	if err := validatePasswordStrength(password); err != nil {
		return nil, err
	}
	if _, err := os.Stat(path); err == nil {
		return nil, fmt.Errorf("store already exists: %s", path)
	} else if !errors.Is(err, os.ErrNotExist) {
		return nil, err
	}
	s := &secureJSONStore{
		path:  filepath.Clean(path),
		kdfN:  defaultKDFN,
		kdfR:  defaultKDFR,
		kdfP:  defaultKDFP,
		plain: internalPlain{Updated: time.Now().UTC(), Data: make(map[string]json.RawMessage)},
	}
	if err := s.initNew(password, generateIdentity, commonName); err != nil {
		return nil, err
	}
	if err := s.Flush(); err != nil {
		return nil, err
	}
	return s, nil
}

// OpenEncryptedStore načte existující soubor a ověří heslo.
func OpenEncryptedStore(path, password string) (SecureJSONStore, error) {
	if password == "" {
		return nil, errors.New("empty password")
	}
	if _, err := os.Stat(path); err != nil {
		return nil, err
	}
	s := &secureJSONStore{path: filepath.Clean(path)}
	if err := s.load(password); err != nil {
		return nil, err
	}
	return s, nil
}

// initNew vytvoří salt, odvodí klíč a vytvoří identitu pokud je třeba.
func (s *secureJSONStore) initNew(password string, generateIdentity bool, commonName string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	salt := make([]byte, 16)
	if _, err := rand.Read(salt); err != nil {
		return err
	}
	key, err := deriveKey(password, salt, s.kdfN, s.kdfR, s.kdfP)
	if err != nil {
		return err
	}
	s.salt = salt
	s.key = key
	if generateIdentity {
		if err := s.generateIdentityLocked(commonName); err != nil {
			return err
		}
	}
	return nil
}

func (s *secureJSONStore) generateIdentityLocked(commonName string) error {
	// RSA 2048
	pk, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}
	privPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
	pubASN1, _ := x509.MarshalPKIXPublicKey(&pk.PublicKey)
	pubPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1})
	// Optional self-signed cert s CommonName uživatele (fallback pokud prázdný)
	if strings.TrimSpace(commonName) == "" {
		commonName = "Local User"
	}
	certPEM, _ := generateSelfSignedCert(pk, commonName)
	_ = s.putLocked("_identity_private_pem", string(privPEM))
	_ = s.putLocked("_identity_public_pem", string(pubPEM))
	_ = s.putLocked("_identity_cert_pem", string(certPEM))
	s.hasIdent = true
	return nil
}

// deriveKey scrypt.
func deriveKey(password string, salt []byte, N, r, p int) ([]byte, error) {
	return scrypt.Key([]byte(password), salt, N, r, p, 32)
}

// load načte soubor, přečte parametry, odvodí klíč a dešifruje.
func (s *secureJSONStore) load(password string) error {
	b, err := os.ReadFile(s.path)
	if err != nil {
		return err
	}
	var env fileEnvelope
	if err := json.Unmarshal(b, &env); err != nil {
		return fmt.Errorf("invalid envelope: %w", err)
	}
	if env.Version != 1 || env.KDF != "scrypt" {
		return errors.New("unsupported version/kdf")
	}
	salt, err := base64.StdEncoding.DecodeString(env.Salt)
	if err != nil {
		return fmt.Errorf("salt b64: %w", err)
	}
	key, err := deriveKey(password, salt, env.N, env.R, env.P)
	if err != nil {
		return err
	}
	nonce, err := base64.StdEncoding.DecodeString(env.Nonce)
	if err != nil {
		return fmt.Errorf("nonce b64: %w", err)
	}
	cBytes, err := base64.StdEncoding.DecodeString(env.Cipher)
	if err != nil {
		return fmt.Errorf("cipher b64: %w", err)
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		return err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return err
	}
	plainBytes, err := gcm.Open(nil, nonce, cBytes, nil)
	if err != nil {
		return errors.New("invalid password or corrupted store")
	}
	var pl internalPlain
	if err := json.Unmarshal(plainBytes, &pl); err != nil {
		return fmt.Errorf("plaintext json: %w", err)
	}
	if pl.Data == nil {
		pl.Data = make(map[string]json.RawMessage)
	}
	s.mu.Lock()
	s.kdfN, s.kdfR, s.kdfP = env.N, env.R, env.P
	s.salt = salt
	s.key = key
	s.plain = pl
	s.dirty = false
	s.hasIdent = s.hasIdentityLocked()
	s.mu.Unlock()
	return nil
}

func (s *secureJSONStore) hasIdentityLocked() bool {
	_, ok1 := s.plain.Data["_identity_private_pem"]
	_, ok2 := s.plain.Data["_identity_public_pem"]
	return ok1 && ok2
}

// Put
func (s *secureJSONStore) Put(key string, value any) error {
	if strings.HasPrefix(key, "_identity_") {
		return errors.New("reserved identity key prefix")
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.putLocked(key, value)
}

func (s *secureJSONStore) putLocked(key string, value any) error {
	if key == "" {
		return errors.New("empty key")
	}
	b, err := json.Marshal(value)
	if err != nil {
		return err
	}
	if s.plain.Data == nil {
		s.plain.Data = make(map[string]json.RawMessage)
	}
	s.plain.Data[key] = json.RawMessage(b)
	s.plain.Updated = time.Now().UTC()
	s.dirty = true
	return nil
}

// Get
func (s *secureJSONStore) Get(key string, dst any) error {
	s.mu.RLock()
	raw, ok := s.plain.Data[key]
	s.mu.RUnlock()
	if !ok {
		return fmt.Errorf("key not found: %s", key)
	}
	return json.Unmarshal(raw, dst)
}

// Delete
func (s *secureJSONStore) Delete(key string) error {
	if strings.HasPrefix(key, "_identity_") {
		return errors.New("cannot delete identity key")
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.plain.Data[key]; !ok {
		return fmt.Errorf("key not found: %s", key)
	}
	delete(s.plain.Data, key)
	s.plain.Updated = time.Now().UTC()
	s.dirty = true
	return nil
}

func (s *secureJSONStore) Has(key string) bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	_, ok := s.plain.Data[key]
	return ok
}

// ListKeys (bez identity interních klíčů)
func (s *secureJSONStore) ListKeys() []string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make([]string, 0, len(s.plain.Data))
	for k := range s.plain.Data {
		if !strings.HasPrefix(k, "_identity_") {
			out = append(out, k)
		}
	}
	return out
}

// Flush uloží na disk.
func (s *secureJSONStore) Flush() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if !s.dirty {
		return nil
	}
	if len(s.key) == 0 {
		return errors.New("store not initialized")
	}
	plainBytes, err := json.Marshal(s.plain)
	if err != nil {
		return err
	}
	block, err := aes.NewCipher(s.key)
	if err != nil {
		return err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return err
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return err
	}
	cipherBytes := gcm.Seal(nil, nonce, plainBytes, nil)
	env := fileEnvelope{
		Version: 1,
		KDF:     "scrypt",
		N:       s.kdfN,
		R:       s.kdfR,
		P:       s.kdfP,
		Salt:    base64.StdEncoding.EncodeToString(s.salt),
		Nonce:   base64.StdEncoding.EncodeToString(nonce),
		Cipher:  base64.StdEncoding.EncodeToString(cipherBytes),
	}
	out, err := json.MarshalIndent(env, "", "  ")
	if err != nil {
		return err
	}
	tmp := s.path + ".tmp"
	if err := os.WriteFile(tmp, out, 0o600); err != nil {
		return err
	}
	if err := os.Rename(tmp, s.path); err != nil {
		return err
	}
	s.dirty = false
	return nil
}

func (s *secureJSONStore) Close() error { return s.Flush() }

// ChangePassword re-derivuje klíč a re-encryptne celý soubor atomicky.
func (s *secureJSONStore) ChangePassword(newPassword string) error {
	if err := validatePasswordStrength(newPassword); err != nil {
		return err
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	newSalt := make([]byte, 16)
	if _, err := rand.Read(newSalt); err != nil {
		return err
	}
	newKey, err := deriveKey(newPassword, newSalt, s.kdfN, s.kdfR, s.kdfP)
	if err != nil {
		return err
	}
	// Dočasně si zapamatuj starý klíč pro rollback, i když tady rollback neimplementujeme – chyba se projeví před přepsáním.
	oldKey, oldSalt := s.key, s.salt
	s.key, s.salt = newKey, newSalt
	s.dirty = true
	if err := s.Flush(); err != nil {
		// rollback (best effort)
		s.key, s.salt = oldKey, oldSalt
		s.dirty = true
		return err
	}
	return nil
}

// Identity helpers
func (s *secureJSONStore) IdentityPrivatePEM() string { return s.getString("_identity_private_pem") }
func (s *secureJSONStore) IdentityPublicPEM() string  { return s.getString("_identity_public_pem") }
func (s *secureJSONStore) IdentityCertPEM() string    { return s.getString("_identity_cert_pem") }

func (s *secureJSONStore) getString(key string) string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if raw, ok := s.plain.Data[key]; ok {
		var v string
		if err := json.Unmarshal(raw, &v); err == nil {
			return v
		}
	}
	return ""
}

// GenerateRandomPassword vytvoří kryptograficky silné heslo ze sady znaků.
func GenerateRandomPassword(length int) (string, error) {
	if length <= 0 {
		return "", errors.New("invalid length")
	}
	const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#-_+="
	buf := make([]byte, length)
	if _, err := rand.Read(buf); err != nil {
		return "", err
	}
	for i, b := range buf {
		buf[i] = alphabet[int(b)%len(alphabet)]
	}
	return string(buf), nil
}

// validatePasswordStrength provede základní kontrolu síly hesla.
// Požadavky: min délka 10, alespoň 3 z kategorií (upper, lower, digit, special !@#-_+=)
func validatePasswordStrength(pw string) error {
	if len(pw) < 10 {
		return errors.New("password too short (min 10)")
	}
	var hasU, hasL, hasD, hasS bool
	for _, r := range pw {
		switch {
		case r >= 'A' && r <= 'Z':
			hasU = true
		case r >= 'a' && r <= 'z':
			hasL = true
		case r >= '0' && r <= '9':
			hasD = true
		default:
			if strings.ContainsRune("!@#-_+=", r) {
				hasS = true
			}
		}
	}
	cats := 0
	if hasU {
		cats++
	}
	if hasL {
		cats++
	}
	if hasD {
		cats++
	}
	if hasS {
		cats++
	}
	if cats < 3 {
		return errors.New("password too weak (need 3 types: upper, lower, digit, special)")
	}
	return nil
}

// ValidatePasswordForUI exportuje validaci pro použití ve Fyne UI.
func ValidatePasswordForUI(pw string) error { return validatePasswordStrength(pw) }
