package legacy

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"

	"ymhut-box/server/unified-management/internal/config"
	"ymhut-box/server/unified-management/internal/db"
)

type Service struct {
	cfg   *config.Config
	store *db.Store
}

type Document struct {
	Name      string                  `json:"name"`
	Raw       string                  `json:"raw"`
	Parsed    map[string]any          `json:"parsed"`
	Path      string                  `json:"path"`
	UpdatedAt string                  `json:"updatedAt"`
	Revisions []db.LegacyJsonRevision `json:"revisions"`
}

type SaveRequest struct {
	Raw    string         `json:"raw"`
	Parsed map[string]any `json:"parsed"`
	Note   string         `json:"note"`
}

func NewService(cfg *config.Config, store *db.Store) *Service {
	return &Service{cfg: cfg, store: store}
}

func (s *Service) EnsureSeedDocuments(ctx context.Context) error {
	for _, name := range []string{"update-info", "media-types"} {
		if err := s.ensureSeedDocument(ctx, name); err != nil {
			return err
		}
	}
	return nil
}

func (s *Service) ensureSeedDocument(ctx context.Context, name string) error {
	fileName, err := fileNameFor(name)
	if err != nil {
		return err
	}
	target := filepath.Join(s.cfg.UpdatePublicDir, fileName)
	if _, err := os.Stat(target); err == nil {
		return nil
	} else if !errors.Is(err, os.ErrNotExist) {
		return err
	}
	source := filepath.Join(s.cfg.LegacyUpdateDir, "public", fileName)
	data, err := os.ReadFile(source)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil
		}
		return err
	}
	_, formatted, err := parseAndFormat(name, data)
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(target), 0o750); err != nil {
		return err
	}
	if err := atomicWrite(target, []byte(formatted)); err != nil {
		return err
	}
	_, _ = s.store.SaveLegacyRevision(name, formatted, "从旧 update/public 初始化基板", "system")
	_ = s.store.InsertAudit(db.AuditLog{Actor: "system", Type: "legacy_json.seeded", Target: name, Message: "已从旧项目导入兼容 JSON 基板"})
	return nil
}

func (s *Service) Get(ctx context.Context, name string) (Document, error) {
	fileName, err := fileNameFor(name)
	if err != nil {
		return Document{}, err
	}
	path := filepath.Join(s.cfg.UpdatePublicDir, fileName)
	data, err := os.ReadFile(path)
	if err != nil {
		return Document{}, err
	}
	parsed, formatted, err := parseAndFormat(name, data)
	if err != nil {
		return Document{}, err
	}
	revisions, _ := s.store.ListLegacyRevisions(name, 20)
	updatedAt := ""
	if info, err := os.Stat(path); err == nil {
		updatedAt = info.ModTime().UTC().Format(time.RFC3339)
	}
	return Document{Name: name, Raw: formatted, Parsed: parsed, Path: path, UpdatedAt: updatedAt, Revisions: revisions}, nil
}

func (s *Service) Validate(ctx context.Context, name string, req SaveRequest) (Document, error) {
	raw, err := requestRaw(req)
	if err != nil {
		return Document{}, err
	}
	parsed, formatted, err := parseAndFormat(name, []byte(raw))
	if err != nil {
		return Document{}, err
	}
	return Document{Name: name, Raw: formatted, Parsed: parsed}, nil
}

func (s *Service) Save(ctx context.Context, name string, req SaveRequest, actor string) (Document, error) {
	fileName, err := fileNameFor(name)
	if err != nil {
		return Document{}, err
	}
	raw, err := requestRaw(req)
	if err != nil {
		return Document{}, err
	}
	parsed, formatted, err := parseAndFormat(name, []byte(raw))
	if err != nil {
		return Document{}, err
	}
	path := filepath.Join(s.cfg.UpdatePublicDir, fileName)
	current, _ := os.ReadFile(path)
	if len(current) > 0 {
		_, _ = s.store.SaveLegacyRevision(name, string(current), "auto backup before save", actor)
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
		return Document{}, err
	}
	if err := atomicWrite(path, []byte(formatted)); err != nil {
		return Document{}, err
	}
	_, _ = s.store.SaveLegacyRevision(name, formatted, req.Note, actor)
	_ = s.store.InsertAudit(db.AuditLog{Actor: actor, Type: "legacy_json.saved", Target: name, Message: legacyMessage(name, "已保存并发布兼容 JSON")})
	revisions, _ := s.store.ListLegacyRevisions(name, 20)
	return Document{Name: name, Raw: formatted, Parsed: parsed, Path: path, UpdatedAt: db.Now(), Revisions: revisions}, nil
}

func (s *Service) Restore(ctx context.Context, name string, revisionID int64, actor string) (Document, error) {
	fileName, err := fileNameFor(name)
	if err != nil {
		return Document{}, err
	}
	revision, err := s.store.GetLegacyRevision(name, revisionID)
	if err != nil {
		return Document{}, err
	}
	parsed, formatted, err := parseAndFormat(name, []byte(revision.Raw))
	if err != nil {
		return Document{}, err
	}
	path := filepath.Join(s.cfg.UpdatePublicDir, fileName)
	current, _ := os.ReadFile(path)
	if len(current) > 0 {
		_, _ = s.store.SaveLegacyRevision(name, string(current), "auto backup before restore", actor)
	}
	if err := atomicWrite(path, []byte(formatted)); err != nil {
		return Document{}, err
	}
	_, _ = s.store.SaveLegacyRevision(name, formatted, "restored revision", actor)
	_ = s.store.InsertAudit(db.AuditLog{Actor: actor, Type: "legacy_json.restored", Target: name, Message: legacyMessage(name, "已恢复兼容 JSON 历史版本")})
	revisions, _ := s.store.ListLegacyRevisions(name, 20)
	return Document{Name: name, Raw: formatted, Parsed: parsed, Path: path, UpdatedAt: db.Now(), Revisions: revisions}, nil
}

func legacyMessage(name, action string) string {
	switch name {
	case "update-info":
		return "更新 JSON：" + action
	case "media-types":
		return "媒体源 JSON：" + action
	default:
		return action
	}
}

func fileNameFor(name string) (string, error) {
	switch strings.TrimSpace(name) {
	case "update-info":
		return "update-info.json", nil
	case "media-types":
		return "media-types.json", nil
	default:
		return "", errors.New("unsupported legacy document")
	}
}

func requestRaw(req SaveRequest) (string, error) {
	if strings.TrimSpace(req.Raw) != "" {
		return req.Raw, nil
	}
	if req.Parsed == nil {
		return "", errors.New("raw or parsed JSON is required")
	}
	data, err := json.Marshal(req.Parsed)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func parseAndFormat(name string, data []byte) (map[string]any, string, error) {
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()
	var parsed map[string]any
	if err := decoder.Decode(&parsed); err != nil {
		return nil, "", err
	}
	if err := validate(name, parsed); err != nil {
		return nil, "", err
	}
	out, err := json.MarshalIndent(parsed, "", "  ")
	if err != nil {
		return nil, "", err
	}
	return parsed, string(out) + "\n", nil
}

func validate(name string, parsed map[string]any) error {
	switch name {
	case "update-info":
		if _, ok := parsed["app_version"]; !ok {
			if _, ok := parsed["title"]; !ok {
				return errors.New("更新 JSON 需要填写 app_version 或 title")
			}
		}
	case "media-types":
		if _, ok := parsed["categories"].([]any); !ok {
			return errors.New("媒体源 JSON 需要包含 categories 数组")
		}
		if _, ok := parsed["layout_version"]; !ok {
			parsed["layout_version"] = "1.0.0"
		}
		if _, ok := parsed["last_updated"]; !ok {
			parsed["last_updated"] = time.Now().UTC().Format(time.RFC3339)
		}
	}
	return nil
}

func atomicWrite(path string, data []byte) error {
	dir := filepath.Dir(path)
	tmp, err := os.CreateTemp(dir, "."+filepath.Base(path)+".*.tmp")
	if err != nil {
		return err
	}
	tmpName := tmp.Name()
	defer os.Remove(tmpName)
	if _, err := tmp.Write(data); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	if err := os.Chmod(tmpName, 0o640); err != nil {
		return err
	}
	return os.Rename(tmpName, path)
}
