package cache

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"time"

	"github.com/tidbcloud/aws-shared-provider/tools/cloud-instances/types"
)

// Manager handles cache operations for all providers
type Manager struct {
	CacheDir string
}

// NewManager creates a new cache manager
func NewManager(cacheDir string) *Manager {
	return &Manager{CacheDir: cacheDir}
}

func (m *Manager) rawDir() string {
	return filepath.Join(m.CacheDir, "raw")
}

func (m *Manager) unifiedDir() string {
	return filepath.Join(m.CacheDir, "unified")
}

// RawPath returns the full path for a raw cache file.
func (m *Manager) RawPath(provider, scope, name string) string {
	return m.getRawFilePath(provider, scope, name)
}

// UnifiedPath returns the full path for a unified cache file.
func (m *Manager) UnifiedPath(provider, region string) string {
	return m.getCacheFilePath(provider, region)
}

// LoadFromCache loads data from a cached file
func (m *Manager) LoadFromCache(provider, region string) (*types.UnifiedData, error) {
	filename := m.getCacheFilePath(provider, region)
	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("failed to read cache file %s: %v", filename, err)
	}

	var unifiedData types.UnifiedData
	if err := json.Unmarshal(data, &unifiedData); err != nil {
		return nil, fmt.Errorf("failed to parse cache file %s: %v", filename, err)
	}

	return &unifiedData, nil
}

// SaveToCache saves data to a cached file
func (m *Manager) SaveToCache(data *types.UnifiedData, provider, region string) error {
	filename := m.getCacheFilePath(provider, region)

	// Ensure directory exists
	dir := filepath.Dir(filename)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create cache directory: %v", err)
	}

	jsonData, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal data: %v", err)
	}

	if err := os.WriteFile(filename, jsonData, 0644); err != nil {
		return fmt.Errorf("failed to write cache file: %v", err)
	}

	return nil
}

// SaveRaw writes raw provider data to disk with restrictive permissions
func (m *Manager) SaveRaw(provider, scope, name string, data []byte) error {
	filename := m.getRawFilePath(provider, scope, name)

	dir := filepath.Dir(filename)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return fmt.Errorf("failed to create raw cache directory: %v", err)
	}

	if err := os.WriteFile(filename, data, 0600); err != nil {
		return fmt.Errorf("failed to write raw cache file: %v", err)
	}

	return nil
}

// LoadRaw reads raw provider data from disk
func (m *Manager) LoadRaw(provider, scope, name string) ([]byte, error) {
	filename := m.getRawFilePath(provider, scope, name)
	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("failed to read raw cache file %s: %v", filename, err)
	}
	return data, nil
}

// RawExists checks if a raw cache file exists
func (m *Manager) RawExists(provider, scope, name string) bool {
	filename := m.getRawFilePath(provider, scope, name)
	_, err := os.Stat(filename)
	return err == nil
}

// FindLatestCache finds the most recent cache file for a provider
func (m *Manager) FindLatestCache(provider string) (string, error) {
	pattern := filepath.Join(m.unifiedDir(), fmt.Sprintf("%s_*.json", provider))
	files, err := filepath.Glob(pattern)
	if err != nil {
		return "", err
	}

	if len(files) == 0 {
		return "", fmt.Errorf("no cache files found for provider %s", provider)
	}

	// Sort by modification time (newest first)
	sort.Slice(files, func(i, j int) bool {
		info1, _ := os.Stat(files[i])
		info2, _ := os.Stat(files[j])
		return info1.ModTime().After(info2.ModTime())
	})

	return files[0], nil
}

// IsCacheValid checks if a cache file is recent enough
func (m *Manager) IsCacheValid(filename string, maxAge time.Duration) bool {
	info, err := os.Stat(filename)
	if err != nil {
		return false
	}

	return time.Since(info.ModTime()) <= maxAge
}

// CacheExists checks if a cache file exists
func (m *Manager) CacheExists(provider, region string) bool {
	filename := m.getCacheFilePath(provider, region)
	_, err := os.Stat(filename)
	return err == nil
}

// GetCacheFilePath returns the full path for a cache file
func (m *Manager) getCacheFilePath(provider, region string) string {
	return filepath.Join(m.unifiedDir(), fmt.Sprintf("%s_%s.json", provider, region))
}

func (m *Manager) getRawFilePath(provider, scope, name string) string {
	return filepath.Join(m.rawDir(), provider, scope, fmt.Sprintf("%s.json", name))
}
