package cert

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type Storage struct {
	basePath string
	certs    map[string]*Certificate
	mu       sync.RWMutex
}

type Certificate struct {
	ID         string `json:"id"`
	StoredPath string `json:"stored_path"`
	Token      string `json:"token,omitempty"`
}

type TokenData struct {
	Token      string
	CertID     string
	StoredPath string
	Password   string
}

func NewStorage(basePath string) *Storage {
	if basePath == "" {
		basePath = "./certs/"
	}
	return &Storage{
		basePath: basePath,
		certs:    make(map[string]*Certificate),
	}
}

func (s *Storage) Init() error {
	if err := os.MkdirAll(s.basePath, 0700); err != nil {
		return fmt.Errorf("creating cert storage directory: %w", err)
	}
	return s.loadFromDisk()
}

func (s *Storage) loadFromDisk() error {
	entries, err := os.ReadDir(s.basePath)
	if err != nil {
		return nil
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		ext := filepath.Ext(entry.Name())
		if ext != ".p12" && ext != ".pfx" {
			continue
		}

		id := entry.Name()[:len(entry.Name())-len(ext)]
		storedPath := filepath.Join(s.basePath, entry.Name())

		s.certs[id] = &Certificate{
			ID:         id,
			StoredPath: storedPath,
		}
	}

	return nil
}

func (s *Storage) StoreFromBase64(id, base64Content string) (string, error) {
	tmpDir := filepath.Join(s.basePath, "tmp")
	if err := os.MkdirAll(tmpDir, 0700); err != nil {
		return "", fmt.Errorf("creating tmp directory: %w", err)
	}

	der, err := base64.StdEncoding.DecodeString(base64Content)
	if err != nil {
		return "", fmt.Errorf("invalid base64: %w", err)
	}

	storedPath := filepath.Join(tmpDir, id+".p12")
	if err := os.WriteFile(storedPath, der, 0600); err != nil {
		return "", fmt.Errorf("writing certificate: %w", err)
	}

	return storedPath, nil
}

func (s *Storage) MoveToPerm(id, tempPath string) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	storedPath := filepath.Join(s.basePath, id+".p12")

	if _, err := os.Stat(storedPath); err == nil {
		if err := os.Remove(storedPath); err != nil {
			return "", fmt.Errorf("removing existing certificate: %w", err)
		}
	}

	if err := os.Rename(tempPath, storedPath); err != nil {
		return "", fmt.Errorf("moving certificate: %w", err)
	}

	s.certs[id] = &Certificate{
		ID:         id,
		StoredPath: storedPath,
	}

	return storedPath, nil
}

func (s *Storage) DeleteTemp(tempPath string) error {
	if err := os.Remove(tempPath); err != nil {
		return fmt.Errorf("deleting temp certificate: %w", err)
	}
	return nil
}

func (s *Storage) Get(id string) (*Certificate, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	cert, ok := s.certs[id]
	if !ok {
		return nil, fmt.Errorf("certificate not found")
	}
	return cert, nil
}

func (s *Storage) Delete(id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	cert, ok := s.certs[id]
	if !ok {
		return fmt.Errorf("certificate not found")
	}

	if err := os.Remove(cert.StoredPath); err != nil {
		return fmt.Errorf("removing certificate file: %w", err)
	}

	delete(s.certs, id)
	return nil
}

func (s *Storage) List() []*Certificate {
	s.mu.RLock()
	defer s.mu.RUnlock()

	certs := make([]*Certificate, 0, len(s.certs))
	for _, cert := range s.certs {
		certs = append(certs, cert)
	}
	return certs
}

func (s *Storage) Hash(data []byte) string {
	hash := sha256.Sum256(data)
	return hex.EncodeToString(hash[:])
}

func (s *Storage) generateToken() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", fmt.Errorf("generating token: %w", err)
	}
	return strings.ToUpper(hex.EncodeToString(b)), nil
}

func (s *Storage) GenerateToken(id, storedPath, password string) (*TokenData, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	token, err := s.generateToken()
	if err != nil {
		return nil, err
	}

	cert, ok := s.certs[id]
	if !ok {
		return nil, fmt.Errorf("certificate not found")
	}
	cert.Token = token

	return &TokenData{
		Token:      token,
		CertID:     id,
		StoredPath: storedPath,
		Password:   password,
	}, nil
}

func (s *Storage) GetByToken(token string) (*TokenData, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, cert := range s.certs {
		if cert.Token == token {
			return &TokenData{
				Token:      cert.Token,
				CertID:     cert.ID,
				StoredPath: cert.StoredPath,
			}, nil
		}
	}
	return nil, fmt.Errorf("token not found")
}
