// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package storage

import (
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"git.quad4.io/Networks/Reticulum-Go/pkg/common"
	"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
)

type Manager struct {
	basePath          string
	ratchetsPath      string
	identitiesPath    string
	destinationTable  string
	knownDestinations string
	transportIdentity string
	mutex             sync.RWMutex
}

type RatchetData struct {
	RatchetKey []byte `msgpack:"ratchet_key"`
	Received   int64  `msgpack:"received"`
}

func NewManager() (*Manager, error) {
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return nil, fmt.Errorf("failed to get home directory: %w", err)
	}

	basePath := filepath.Join(homeDir, ".reticulum-go", "storage")

	m := &Manager{
		basePath:          basePath,
		ratchetsPath:      filepath.Join(basePath, "ratchets"),
		identitiesPath:    filepath.Join(basePath, "identities"),
		destinationTable:  filepath.Join(basePath, "destination_table"),
		knownDestinations: filepath.Join(basePath, "known_destinations"),
		transportIdentity: filepath.Join(basePath, "transport_identity"),
	}

	if err := m.initializeDirectories(); err != nil {
		return nil, err
	}

	return m, nil
}

func (m *Manager) initializeDirectories() error {
	dirs := []string{
		m.basePath,
		m.ratchetsPath,
		m.identitiesPath,
		filepath.Join(m.basePath, "cache"),
		filepath.Join(m.basePath, "cache", "announces"),
		filepath.Join(m.basePath, "resources"),
	}

	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0700); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", dir, err)
		}
	}

	return nil
}

func (m *Manager) SaveRatchet(identityHash []byte, ratchetKey []byte) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	hexHash := hex.EncodeToString(identityHash)
	ratchetDir := filepath.Join(m.ratchetsPath, hexHash)

	if err := os.MkdirAll(ratchetDir, 0700); err != nil {
		return fmt.Errorf("failed to create ratchet directory: %w", err)
	}

	ratchetData := RatchetData{
		RatchetKey: ratchetKey,
		Received:   time.Now().Unix(),
	}

	data, err := common.MsgpackMarshal(ratchetData)
	if err != nil {
		return fmt.Errorf("failed to marshal ratchet data: %w", err)
	}

	ratchetHash := hex.EncodeToString(ratchetKey[:16])
	outPath := filepath.Join(ratchetDir, ratchetHash+".out")
	finalPath := filepath.Join(ratchetDir, ratchetHash)

	if err := os.WriteFile(outPath, data, 0600); err != nil {
		return fmt.Errorf("failed to write ratchet file: %w", err)
	}

	if err := os.Rename(outPath, finalPath); err != nil {
		_ = os.Remove(outPath)
		return fmt.Errorf("failed to move ratchet file: %w", err)
	}

	debug.Log(debug.DEBUG_VERBOSE, "Saved ratchet to storage", "identity", hexHash, "ratchet", ratchetHash)
	return nil
}

func (m *Manager) LoadRatchets(identityHash []byte) (map[string][]byte, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	hexHash := hex.EncodeToString(identityHash)
	ratchetDir := filepath.Join(m.ratchetsPath, hexHash)

	ratchets := make(map[string][]byte)

	if _, err := os.Stat(ratchetDir); os.IsNotExist(err) {
		debug.Log(debug.DEBUG_VERBOSE, "No ratchet directory found", "identity", hexHash)
		return ratchets, nil
	}

	entries, err := os.ReadDir(ratchetDir)
	if err != nil {
		return nil, fmt.Errorf("failed to read ratchet directory: %w", err)
	}

	now := time.Now().Unix()
	expiry := int64(2592000) // 30 days

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

		filePath := filepath.Join(ratchetDir, entry.Name())
		// bearer:disable go_gosec_filesystem_filereadtaint
		data, err := os.ReadFile(filePath) // #nosec G304 - reading from controlled directory
		if err != nil {
			debug.Log(debug.DEBUG_ERROR, "Failed to read ratchet file", "file", entry.Name(), "error", err)
			continue
		}

		var ratchetData RatchetData
		if err := common.MsgpackUnmarshal(data, &ratchetData); err != nil {
			debug.Log(debug.DEBUG_ERROR, "Corrupted ratchet data", "file", entry.Name(), "error", err)
			_ = os.Remove(filePath)
			continue
		}

		if now > ratchetData.Received+expiry {
			debug.Log(debug.DEBUG_VERBOSE, "Removing expired ratchet", "file", entry.Name())
			_ = os.Remove(filePath)
			continue
		}

		ratchetHash := entry.Name()
		ratchets[ratchetHash] = ratchetData.RatchetKey
	}

	debug.Log(debug.DEBUG_VERBOSE, "Loaded ratchets from storage", "identity", hexHash, "count", len(ratchets))
	return ratchets, nil
}

func (m *Manager) GetBasePath() string {
	return m.basePath
}

func (m *Manager) GetRatchetsPath() string {
	return m.ratchetsPath
}

func (m *Manager) GetIdentityPath() string {
	return filepath.Join(m.basePath, "identity")
}

func (m *Manager) GetTransportIdentityPath() string {
	return m.transportIdentity
}

func (m *Manager) GetDestinationTablePath() string {
	return m.destinationTable
}

func (m *Manager) GetKnownDestinationsPath() string {
	return m.knownDestinations
}
