package storage

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"

	"mengyastore-backend/internal/models"
)

const defaultCoverURL = "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png"
const viewCooldown = 6 * time.Hour
const maxScreenshotURLs = 5

type JSONStore struct {
	path        string
	mu          sync.Mutex
	recentViews map[string]time.Time
}

func NewJSONStore(path string) (*JSONStore, error) {
	if err := ensureProductsFile(path); err != nil {
		return nil, err
	}
	return &JSONStore{
		path:        path,
		recentViews: make(map[string]time.Time),
	}, nil
}

func ensureProductsFile(path string) error {
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fmt.Errorf("mkdir data dir: %w", err)
	}
	if _, err := os.Stat(path); err == nil {
		return nil
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("stat data file: %w", err)
	}

	initial := []models.Product{}
	bytes, err := json.MarshalIndent(initial, "", "  ")
	if err != nil {
		return fmt.Errorf("init json: %w", err)
	}
	if err := os.WriteFile(path, bytes, 0o644); err != nil {
		return fmt.Errorf("write init json: %w", err)
	}
	return nil
}

func (s *JSONStore) ListAll() ([]models.Product, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.readAll()
}

func (s *JSONStore) ListActive() ([]models.Product, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	items, err := s.readAll()
	if err != nil {
		return nil, err
	}
	active := make([]models.Product, 0, len(items))
	for _, item := range items {
		if item.Active {
			active = append(active, item)
		}
	}
	return active, nil
}

func (s *JSONStore) GetByID(id string) (models.Product, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	items, err := s.readAll()
	if err != nil {
		return models.Product{}, err
	}
	for _, item := range items {
		if item.ID == id {
			return item, nil
		}
	}
	return models.Product{}, fmt.Errorf("product not found")
}

func (s *JSONStore) Create(p models.Product) (models.Product, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	items, err := s.readAll()
	if err != nil {
		return models.Product{}, err
	}
	p = normalizeProduct(p)
	p.ID = uuid.NewString()
	now := time.Now()
	p.CreatedAt = now
	p.UpdatedAt = now
	items = append(items, p)
	if err := s.writeAll(items); err != nil {
		return models.Product{}, err
	}
	return p, nil
}

func (s *JSONStore) Update(id string, patch models.Product) (models.Product, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	items, err := s.readAll()
	if err != nil {
		return models.Product{}, err
	}
	for i, item := range items {
		if item.ID == id {
			normalized := normalizeProduct(patch)
			item.Name = normalized.Name
			item.Price = normalized.Price
			item.DiscountPrice = normalized.DiscountPrice
			item.Tags = normalized.Tags
			item.CoverURL = normalized.CoverURL
			item.ScreenshotURLs = normalized.ScreenshotURLs
			item.VerificationURL = normalized.VerificationURL
			item.Codes = normalized.Codes
			item.Quantity = normalized.Quantity
			item.Description = normalized.Description
			item.Active = normalized.Active
			item.UpdatedAt = time.Now()
			items[i] = item
			if err := s.writeAll(items); err != nil {
				return models.Product{}, err
			}
			return item, nil
		}
	}
	return models.Product{}, fmt.Errorf("product not found")
}

func (s *JSONStore) Toggle(id string, active bool) (models.Product, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	items, err := s.readAll()
	if err != nil {
		return models.Product{}, err
	}
	for i, item := range items {
		if item.ID == id {
			item.Active = active
			item.UpdatedAt = time.Now()
			items[i] = item
			if err := s.writeAll(items); err != nil {
				return models.Product{}, err
			}
			return item, nil
		}
	}
	return models.Product{}, fmt.Errorf("product not found")
}

func (s *JSONStore) IncrementView(id, fingerprint string) (models.Product, bool, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	items, err := s.readAll()
	if err != nil {
		return models.Product{}, false, err
	}

	now := time.Now()
	s.cleanupRecentViews(now)
	key := buildViewKey(id, fingerprint)
	if lastViewedAt, ok := s.recentViews[key]; ok && now.Sub(lastViewedAt) < viewCooldown {
		for _, item := range items {
			if item.ID == id {
				return item, false, nil
			}
		}
		return models.Product{}, false, fmt.Errorf("product not found")
	}

	for i, item := range items {
		if item.ID == id {
			item.ViewCount++
			item.UpdatedAt = now
			items[i] = item
			s.recentViews[key] = now
			if err := s.writeAll(items); err != nil {
				return models.Product{}, false, err
			}
			return item, true, nil
		}
	}

	return models.Product{}, false, fmt.Errorf("product not found")
}

func (s *JSONStore) Delete(id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	items, err := s.readAll()
	if err != nil {
		return err
	}
	filtered := make([]models.Product, 0, len(items))
	for _, item := range items {
		if item.ID != id {
			filtered = append(filtered, item)
		}
	}
	if err := s.writeAll(filtered); err != nil {
		return err
	}
	return nil
}

func (s *JSONStore) readAll() ([]models.Product, error) {
	bytes, err := os.ReadFile(s.path)
	if err != nil {
		return nil, fmt.Errorf("read products: %w", err)
	}
	var items []models.Product
	if err := json.Unmarshal(bytes, &items); err != nil {
		return nil, fmt.Errorf("parse products: %w", err)
	}
	for i, item := range items {
		items[i] = normalizeProduct(item)
	}
	return items, nil
}

func (s *JSONStore) writeAll(items []models.Product) error {
	for i, item := range items {
		items[i] = normalizeProduct(item)
	}
	bytes, err := json.MarshalIndent(items, "", "  ")
	if err != nil {
		return fmt.Errorf("encode products: %w", err)
	}
	if err := os.WriteFile(s.path, bytes, 0o644); err != nil {
		return fmt.Errorf("write products: %w", err)
	}
	return nil
}

func normalizeProduct(item models.Product) models.Product {
	item.CoverURL = strings.TrimSpace(item.CoverURL)
	if item.CoverURL == "" {
		item.CoverURL = defaultCoverURL
	}
	if item.Tags == nil {
		item.Tags = []string{}
	}
	item.Tags = sanitizeTags(item.Tags)
	if item.ScreenshotURLs == nil {
		item.ScreenshotURLs = []string{}
	}
	if len(item.ScreenshotURLs) > maxScreenshotURLs {
		item.ScreenshotURLs = item.ScreenshotURLs[:maxScreenshotURLs]
	}
	if item.Codes == nil {
		item.Codes = []string{}
	}
	if item.DiscountPrice <= 0 || item.DiscountPrice >= item.Price {
		item.DiscountPrice = 0
	}
	item.VerificationURL = strings.TrimSpace(item.VerificationURL)
	item.Codes = sanitizeCodes(item.Codes)
	item.Quantity = len(item.Codes)
	return item
}

func sanitizeCodes(codes []string) []string {
	clean := make([]string, 0, len(codes))
	seen := map[string]bool{}
	for _, code := range codes {
		trimmed := strings.TrimSpace(code)
		if trimmed == "" {
			continue
		}
		if seen[trimmed] {
			continue
		}
		seen[trimmed] = true
		clean = append(clean, trimmed)
	}
	return clean
}

func sanitizeTags(tags []string) []string {
	clean := make([]string, 0, len(tags))
	seen := map[string]bool{}
	for _, tag := range tags {
		t := strings.TrimSpace(tag)
		if t == "" {
			continue
		}
		key := strings.ToLower(t)
		if seen[key] {
			continue
		}
		seen[key] = true
		clean = append(clean, t)
		if len(clean) >= 20 {
			break
		}
	}
	return clean
}

func buildViewKey(id, fingerprint string) string {
	sum := sha256.Sum256([]byte(id + "|" + fingerprint))
	return fmt.Sprintf("%x", sum)
}

func (s *JSONStore) cleanupRecentViews(now time.Time) {
	for key, lastViewedAt := range s.recentViews {
		if now.Sub(lastViewedAt) >= viewCooldown {
			delete(s.recentViews, key)
		}
	}
}
