package mq

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	amqp "github.com/rabbitmq/amqp091-go"

	"mengyastore-backend/internal/storage"
)

// Client 封装单条 AMQP 连接、发布用 Channel，以及单环境的命名。
type Client struct {
	conn      *amqp.Connection
	pubCh     *amqp.Channel
	exchange  string
	queue     string
	env       string
	amqpURL   string
	mu        sync.Mutex
	closing   sync.Once
	connected bool

	// 用于重连后重启消费协程（与 StartConsumer 注入的一致）
	consumerCtx  context.Context
	consumerSite *storage.SiteStore
}

// New 连接 RabbitMQ 并声明交换机 + 队列 + 绑定（幂等）。
func New(amqpURL, env string) (*Client, error) {
	if amqpURL == "" {
		return nil, fmt.Errorf("empty amqp url")
	}
	conn, err := amqp.DialConfig(amqpURL, amqp.Config{
		Heartbeat: 30 * time.Second,
		Locale:    "en_US",
	})
	if err != nil {
		return nil, fmt.Errorf("amqp dial: %w", err)
	}

	pubCh, err := conn.Channel()
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("amqp channel: %w", err)
	}

	exchange, queue, err := declareTopology(pubCh, env)
	if err != nil {
		pubCh.Close()
		conn.Close()
		return nil, err
	}

	return &Client{
		conn:      conn,
		pubCh:     pubCh,
		exchange:  exchange,
		queue:     queue,
		env:       sanitizeEnv(env),
		amqpURL:   amqpURL,
		connected: true,
	}, nil
}

func isRecoverableAMQP(err error) bool {
	if err == nil {
		return false
	}
	var e *amqp.Error
	if errors.As(err, &e) {
		// 504 channel/connection not open、320 连接被服务端关闭等，通过重连恢复
		if e.Code == amqp.ChannelError || e.Code == amqp.ConnectionForced {
			return true
		}
		return strings.Contains(strings.ToLower(e.Reason), "not open")
	}
	s := strings.ToLower(err.Error())
	return strings.Contains(s, "channel/connection is not open") ||
		strings.Contains(s, "connection closed") ||
		strings.Contains(s, "use of closed network connection") ||
		strings.Contains(s, "eof")
}

// reconnectLocked 在持有 mu 时调用：关闭旧连接并重新拨号、声明拓扑。
func (c *Client) reconnectLocked() error {
	if !c.connected || c.amqpURL == "" {
		return fmt.Errorf("mq client closed")
	}

	if c.pubCh != nil {
		_ = c.pubCh.Close()
		c.pubCh = nil
	}
	if c.conn != nil && !c.conn.IsClosed() {
		_ = c.conn.Close()
	}
	c.conn = nil

	conn, err := amqp.DialConfig(c.amqpURL, amqp.Config{
		Heartbeat: 30 * time.Second,
		Locale:    "en_US",
	})
	if err != nil {
		return fmt.Errorf("amqp reconnect dial: %w", err)
	}
	pubCh, err := conn.Channel()
	if err != nil {
		conn.Close()
		return fmt.Errorf("amqp reconnect channel: %w", err)
	}
	exchange, queue, err := declareTopology(pubCh, c.env)
	if err != nil {
		pubCh.Close()
		conn.Close()
		return err
	}
	c.conn = conn
	c.pubCh = pubCh
	c.exchange = exchange
	c.queue = queue

	if c.consumerCtx != nil && c.consumerSite != nil && c.consumerCtx.Err() == nil {
		go RunOrderEmailConsumer(c.consumerCtx, c.conn, c.env, c.consumerSite)
		log.Printf("[MQ] consumer restarted after reconnect (queue=%s)", c.queue)
	}
	return nil
}

func (c *Client) passiveQueueLocked() error {
	if c.pubCh == nil || !c.connected {
		return fmt.Errorf("mq client closed")
	}
	_, err := c.pubCh.QueueDeclarePassive(c.queue, true, false, false, false, nil)
	return err
}

// PublishOrderEmail 向 order-email 路由键发布一条持久化 JSON 消息。
func (c *Client) PublishOrderEmail(ctx context.Context, p OrderEmailPayload) error {
	body, err := json.Marshal(p)
	if err != nil {
		return fmt.Errorf("marshal payload: %w", err)
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	if c.pubCh == nil || !c.connected {
		return fmt.Errorf("mq client closed")
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}

	tryPublish := func() error {
		return c.pubCh.PublishWithContext(ctx,
			c.exchange,
			routingKeyOrderEmail,
			false,
			false,
			amqp.Publishing{
				ContentType:  "application/json",
				DeliveryMode: amqp.Persistent,
				Timestamp:    time.Now(),
				Body:         body,
			},
		)
	}

	err = tryPublish()
	if err != nil && isRecoverableAMQP(err) {
		if rerr := c.reconnectLocked(); rerr == nil {
			err = tryPublish()
		} else {
			err = fmt.Errorf("%w (reconnect: %v)", err, rerr)
		}
	}
	if err != nil {
		return fmt.Errorf("publish: %w", err)
	}
	return nil
}

// QueueInspectInfo 返回当前队列深度与消费者数（被动声明查询）。
func (c *Client) QueueInspectInfo() (messages int, consumers int, err error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	try := func() (int, int, error) {
		if c.pubCh == nil || !c.connected {
			return 0, 0, fmt.Errorf("mq client closed")
		}
		q, e := c.pubCh.QueueDeclarePassive(c.queue, true, false, false, false, nil)
		if e != nil {
			return 0, 0, e
		}
		return q.Messages, q.Consumers, nil
	}

	msgs, cons, err := try()
	if err != nil && isRecoverableAMQP(err) {
		if rerr := c.reconnectLocked(); rerr != nil {
			return 0, 0, fmt.Errorf("%w (reconnect: %v)", err, rerr)
		}
		return try()
	}
	return msgs, cons, err
}

// Ping 检查发布 Channel 能否对声明的队列做被动查询（供 /api/health 探活）。
func (c *Client) Ping() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	err := c.passiveQueueLocked()
	if err != nil && isRecoverableAMQP(err) {
		if rerr := c.reconnectLocked(); rerr != nil {
			return fmt.Errorf("%w (reconnect: %v)", err, rerr)
		}
		return c.passiveQueueLocked()
	}
	return err
}

// Env 返回交换机/队列名使用的环境后缀（已规范化）。
func (c *Client) Env() string { return c.env }

// StartConsumer 在 ctx 取消前运行订单邮件消费者（通常在 goroutine 中调用）。
func (c *Client) StartConsumer(ctx context.Context, site *storage.SiteStore) {
	c.mu.Lock()
	c.consumerCtx = ctx
	c.consumerSite = site
	conn := c.conn
	env := c.env
	c.mu.Unlock()
	if conn == nil || site == nil {
		return
	}
	RunOrderEmailConsumer(ctx, conn, env, site)
}

// Close 关闭发布 Channel 与连接。
func (c *Client) Close() {
	c.closing.Do(func() {
		c.mu.Lock()
		defer c.mu.Unlock()
		c.connected = false
		c.consumerCtx = nil
		c.consumerSite = nil
		if c.pubCh != nil {
			_ = c.pubCh.Close()
			c.pubCh = nil
		}
		if c.conn != nil {
			_ = c.conn.Close()
			c.conn = nil
		}
	})
}
