package service

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"

	"git.iioio.com/freefire/jiang13-forum/model"
)

// AppVersion 由 cmd 通过 SetAppVersion 注入（ldflags）
var AppVersion = "dev"

// SetAppVersion 设置运行时版本号
func SetAppVersion(v string) {
	v = strings.TrimSpace(v)
	if v != "" {
		AppVersion = v
	}
}

func newCommunityInstanceID() string {
	return uuid.NewString()
}

const (
	communityHeartbeatInterval  = 24 * time.Hour
	communityHeartbeatTimeout   = 8 * time.Second
	communityOnlineWithin       = 72 * time.Hour
	maxCommunitySiteURLLen      = 512
	maxCommunitySiteNameLen     = 128
	maxCommunityVersionLen      = 32
	maxCommunityInstanceIDLen   = 64
	maxCommunityFeaturedNoteLen = 64
)

var (
	ErrCommunityHubDisabled = errors.New("本站未开启社区枢纽")
	ErrCommunityBadPayload  = errors.New("心跳参数无效")

	// communityHubBaseURL 出站枢纽根地址（写死官方站；测试可临时覆盖）
	communityHubBaseURL = DefaultCommunityHubURL
)

// CommunityHeartbeatPayload 出站 / 入站心跳体
type CommunityHeartbeatPayload struct {
	InstanceID string `json:"instance_id"`
	SiteURL    string `json:"site_url"`
	SiteName   string `json:"site_name"`
	Version    string `json:"version"`
	Users      int64  `json:"users"`
	Posts      int64  `json:"posts"`
}

// CommunityInstanceView 管理端列表项
type CommunityInstanceView struct {
	InstanceID   string    `json:"instance_id"`
	SiteURL      string    `json:"site_url"`
	SiteName     string    `json:"site_name"`
	Version      string    `json:"version"`
	Users        int64     `json:"users"`
	Posts        int64     `json:"posts"`
	FirstSeenAt  time.Time `json:"first_seen_at"`
	LastSeenAt   time.Time `json:"last_seen_at"`
	Online       bool      `json:"online"`
	Featured     bool      `json:"featured"`
	FeaturedNote string    `json:"featured_note"`
}

// CommunityShowcaseItem 公开展柜条目（不含敏感字段）
type CommunityShowcaseItem struct {
	SiteURL      string `json:"site_url"`
	SiteName     string `json:"site_name"`
	Version      string `json:"version"`
	FeaturedNote string `json:"featured_note,omitempty"`
}

// CommunityFeatureInput 管理端精选请求
type CommunityFeatureInput struct {
	Featured     bool   `json:"featured"`
	FeaturedNote string `json:"featured_note"`
}

// CommunityService 可选社区上报 + 枢纽接收
type CommunityService struct {
	settings *ForumSettingsService
	client   *http.Client
	stopCh   chan struct{}
	wg       sync.WaitGroup
	kickCh   chan struct{}
}

// NewCommunityService 创建社区服务
func NewCommunityService(settings *ForumSettingsService) *CommunityService {
	return &CommunityService{
		settings: settings,
		client:   &http.Client{Timeout: communityHeartbeatTimeout},
		stopCh:   make(chan struct{}),
		kickCh:   make(chan struct{}, 1),
	}
}

// StartBackground 启动 24h 心跳循环
func (c *CommunityService) StartBackground() {
	c.wg.Add(1)
	go func() {
		defer c.wg.Done()
		timer := time.NewTimer(30 * time.Second)
		defer timer.Stop()
		for {
			select {
			case <-c.stopCh:
				return
			case <-c.kickCh:
				c.trySendHeartbeat()
				if !timer.Stop() {
					select {
					case <-timer.C:
					default:
					}
				}
				timer.Reset(communityHeartbeatInterval)
			case <-timer.C:
				c.trySendHeartbeat()
				timer.Reset(communityHeartbeatInterval)
			}
		}
	}()
}

// Stop 停止后台心跳
func (c *CommunityService) Stop() {
	select {
	case <-c.stopCh:
	default:
		close(c.stopCh)
	}
	c.wg.Wait()
}

// KickHeartbeat 请求尽快发送一次心跳（开启上报时调用）
func (c *CommunityService) KickHeartbeat() {
	select {
	case c.kickCh <- struct{}{}:
	default:
	}
}

func (c *CommunityService) trySendHeartbeat() {
	_ = c.SendHeartbeatOnce("")
}

// SendHeartbeatOnce 立即发送一次心跳；requestOrigin 可在管理端保存时传入以补全本站地址
func (c *CommunityService) SendHeartbeatOnce(requestOrigin string) error {
	// 枢纽站（含官网）不出站上报，避免自己心跳给自己
	if c.settings.CommunityHubEnabled(requestOrigin) {
		return nil
	}
	cfg := c.settings.CommunityConfig()
	if !cfg.ReportEnabled {
		return nil
	}
	if requestOrigin != "" {
		if _, err := c.settings.EnsureCommunitySiteURL(requestOrigin); err != nil {
			log.Printf("[community] 组装心跳失败: %v", err)
			return err
		}
	}
	payload, err := c.buildPayload(requestOrigin)
	if err != nil {
		log.Printf("[community] 组装心跳失败: %v", err)
		return err
	}
	hub := strings.TrimRight(communityHubBaseURL, "/")
	if hub == "" {
		hub = DefaultCommunityHubURL
	}
	endpoint := hub + "/api/community/heartbeat"
	body, _ := json.Marshal(payload)
	req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body))
	if err != nil {
		log.Printf("[community] 创建请求失败: %v", err)
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "jiang13-forum/"+AppVersion)
	resp, err := c.client.Do(req)
	if err != nil {
		log.Printf("[community] 上报失败: %v", err)
		return err
	}
	defer resp.Body.Close()
	_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		err := fmt.Errorf("上报被拒绝: HTTP %d", resp.StatusCode)
		log.Printf("[community] %v", err)
		return err
	}
	return nil
}

func (c *CommunityService) buildPayload(requestOrigin string) (*CommunityHeartbeatPayload, error) {
	id, err := c.settings.EnsureCommunityInstanceID()
	if err != nil {
		return nil, err
	}
	siteURL := c.settings.CommunitySiteURL(requestOrigin)
	if siteURL == "" {
		return nil, fmt.Errorf("无法确定本站公开地址：请先在 OIDC 设置中填写 ROOT_URL，或通过浏览器管理端开启上报")
	}
	var users, posts int64
	_ = model.DB.Model(&model.User{}).Count(&users).Error
	_ = model.DB.Model(&model.Post{}).Where("status = ?", model.ContentStatusPublished).Count(&posts).Error
	brand := c.settings.SiteBranding()
	return &CommunityHeartbeatPayload{
		InstanceID: id,
		SiteURL:    truncateRunes(siteURL, maxCommunitySiteURLLen),
		SiteName:   truncateRunes(brand.Name, maxCommunitySiteNameLen),
		Version:    truncateRunes(AppVersion, maxCommunityVersionLen),
		Users:      users,
		Posts:      posts,
	}, nil
}

// ReceiveHeartbeat 枢纽接收心跳并 upsert；requestHint 用于按请求 Host 识别官网
func (c *CommunityService) ReceiveHeartbeat(in CommunityHeartbeatPayload, remoteIP, requestHint string) error {
	if !c.settings.CommunityHubEnabled(requestHint) {
		return ErrCommunityHubDisabled
	}
	in.InstanceID = strings.TrimSpace(in.InstanceID)
	in.SiteURL = strings.TrimSpace(in.SiteURL)
	in.SiteName = strings.TrimSpace(in.SiteName)
	in.Version = strings.TrimSpace(in.Version)
	if in.InstanceID == "" || len(in.InstanceID) > maxCommunityInstanceIDLen {
		return ErrCommunityBadPayload
	}
	if err := validateCommunitySiteURL(in.SiteURL); err != nil {
		return err
	}
	in.SiteURL = truncateRunes(in.SiteURL, maxCommunitySiteURLLen)
	in.SiteName = truncateRunes(in.SiteName, maxCommunitySiteNameLen)
	in.Version = truncateRunes(in.Version, maxCommunityVersionLen)
	if in.Users < 0 {
		in.Users = 0
	}
	if in.Posts < 0 {
		in.Posts = 0
	}
	now := time.Now()
	var row model.CommunityInstance
	res := model.DB.Where("instance_id = ?", in.InstanceID).Limit(1).Find(&row)
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		row = model.CommunityInstance{
			InstanceID:  in.InstanceID,
			SiteURL:     in.SiteURL,
			SiteName:    in.SiteName,
			Version:     in.Version,
			Users:       in.Users,
			Posts:       in.Posts,
			RemoteIP:    truncateRunes(remoteIP, 64),
			FirstSeenAt: now,
			LastSeenAt:  now,
		}
		return model.DB.Create(&row).Error
	}
	row.SiteURL = in.SiteURL
	row.SiteName = in.SiteName
	row.Version = in.Version
	row.Users = in.Users
	row.Posts = in.Posts
	row.RemoteIP = truncateRunes(remoteIP, 64)
	row.LastSeenAt = now
	return model.DB.Save(&row).Error
}

// ListInstances 管理端实例列表（按最近心跳倒序）
func (c *CommunityService) ListInstances() ([]CommunityInstanceView, error) {
	var rows []model.CommunityInstance
	if err := model.DB.Order("last_seen_at DESC").Find(&rows).Error; err != nil {
		return nil, err
	}
	now := time.Now()
	out := make([]CommunityInstanceView, 0, len(rows))
	for _, r := range rows {
		out = append(out, CommunityInstanceView{
			InstanceID:   r.InstanceID,
			SiteURL:      r.SiteURL,
			SiteName:     r.SiteName,
			Version:      r.Version,
			Users:        r.Users,
			Posts:        r.Posts,
			FirstSeenAt:  r.FirstSeenAt,
			LastSeenAt:   r.LastSeenAt,
			Online:       now.Sub(r.LastSeenAt) <= communityOnlineWithin,
			Featured:     r.Featured,
			FeaturedNote: r.FeaturedNote,
		})
	}
	return out, nil
}

// SetInstanceFeatured 人工精选 / 取消；心跳无法自助上柜
func (c *CommunityService) SetInstanceFeatured(instanceID string, in CommunityFeatureInput) (*CommunityInstanceView, error) {
	instanceID = strings.TrimSpace(instanceID)
	if instanceID == "" {
		return nil, ErrCommunityBadPayload
	}
	var row model.CommunityInstance
	if err := model.DB.Where("instance_id = ?", instanceID).First(&row).Error; err != nil {
		return nil, err
	}
	row.Featured = in.Featured
	if in.Featured {
		row.FeaturedNote = truncateRunes(strings.TrimSpace(in.FeaturedNote), maxCommunityFeaturedNoteLen)
	} else {
		row.FeaturedNote = ""
	}
	if err := model.DB.Save(&row).Error; err != nil {
		return nil, err
	}
	now := time.Now()
	return &CommunityInstanceView{
		InstanceID:   row.InstanceID,
		SiteURL:      row.SiteURL,
		SiteName:     row.SiteName,
		Version:      row.Version,
		Users:        row.Users,
		Posts:        row.Posts,
		FirstSeenAt:  row.FirstSeenAt,
		LastSeenAt:   row.LastSeenAt,
		Online:       now.Sub(row.LastSeenAt) <= communityOnlineWithin,
		Featured:     row.Featured,
		FeaturedNote: row.FeaturedNote,
	}, nil
}

// ListShowcase 公开展柜：仅精选；枢纽关闭时返回空；requestHint 用于按 Host 识别官网
func (c *CommunityService) ListShowcase(requestHint string) ([]CommunityShowcaseItem, error) {
	if !c.settings.CommunityHubEnabled(requestHint) {
		return []CommunityShowcaseItem{}, nil
	}
	var rows []model.CommunityInstance
	if err := model.DB.Where("featured = ?", true).Order("last_seen_at DESC").Find(&rows).Error; err != nil {
		return nil, err
	}
	out := make([]CommunityShowcaseItem, 0, len(rows))
	for _, r := range rows {
		if validateCommunitySiteURL(r.SiteURL) != nil {
			continue
		}
		out = append(out, CommunityShowcaseItem{
			SiteURL:      r.SiteURL,
			SiteName:     r.SiteName,
			Version:      r.Version,
			FeaturedNote: r.FeaturedNote,
		})
	}
	return out, nil
}

func validateCommunitySiteURL(raw string) error {
	u, err := url.Parse(raw)
	if err != nil || u.Scheme == "" || u.Host == "" {
		return ErrCommunityBadPayload
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return ErrCommunityBadPayload
	}
	return nil
}
