package main

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"github.com/lib/pq"
	_ "github.com/lib/pq"
	"github.com/redis/go-redis/v9"
)

type Order struct {
	ID             string     `json:"id"`
	Type           string     `json:"type"`
	Status         string     `json:"status"`
	Price          int64      `json:"price"`
	Point          int64      `json:"point"`
	PriceFrom      *int64     `json:"price_from"`
	PriceTo        *int64     `json:"price_to"`
	BankID         string     `json:"bank_id"`
	LoanTypeID     string     `json:"loan_type_id"`
	PercentID      string     `json:"percent_id"`
	TimeID         string     `json:"time_id"`
	CreatedAt      time.Time  `json:"created_at"`
	MatchExpiresAt *time.Time `json:"match_expires_at"`
}

func (o *Order) UnmarshalJSON(data []byte) error {
	type Alias Order
	aux := &struct {
		CreatedAt string `json:"created_at"`
		*Alias
	}{
		Alias: (*Alias)(o),
	}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	formats := []string{
		time.RFC3339,
		"2006-01-02T15:04:05Z",
		"2006-01-02T15:04:05",
		"2006-01-02 15:04:05",
	}

	var parseErr error
	for _, format := range formats {
		t, err := time.Parse(format, aux.CreatedAt)
		if err == nil {
			o.CreatedAt = t
			return nil
		}
		parseErr = err
	}

	return fmt.Errorf("failed to parse created_at '%s': %w", aux.CreatedAt, parseErr)
}

type Match struct {
	ID           string    `json:"id"`
	BuyOrderID   string    `json:"buy_order_id"`
	SellOrderID  string    `json:"sell_order_id"`
	MatchedPrice int64     `json:"matched_price"`
	MatchedAt    time.Time `json:"matched_at"`
}

type MatchEvent struct {
	Match     Match  `json:"match"`
	BuyOrder  Order  `json:"buy_order"`
	SellOrder Order  `json:"sell_order"`
	EventType string `json:"event_type"`
}

type OrderBook struct {
	BuyOrders  []Order
	SellOrders []Order
	Blocklist  map[string]bool
	mu         sync.RWMutex
}

var (
	db          *sql.DB
	redisClient *redis.Client
	orderBook   = &OrderBook{
		Blocklist: make(map[string]bool),
	}
	upgrader   = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
	clients    = make(map[*websocket.Conn]bool)
	clientsMu  sync.RWMutex
	eventQueue = make(chan MatchEvent, 100)
)

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

func getDatabaseURL() string {
	host := getEnv("DB_HOST", "postgres")
	port := getEnv("DB_PORT", "5432")
	user := getEnv("DB_USER", "postgres")
	password := getEnv("DB_PASSWORD", "secret")
	dbname := getEnv("DB_NAME", "loan")

	return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
		host, port, user, password, dbname)
}

func initRedis() {
	redisClient = redis.NewClient(&redis.Options{
		Addr:     getEnv("REDIS_ADDR", "redis:6379"),
		Password: getEnv("REDIS_PASSWORD", ""),
		DB:       0,
	})

	ctx := context.Background()
	if err := redisClient.Ping(ctx).Err(); err != nil {
		log.Fatal("Failed to connect to Redis:", err)
	}

	log.Println("✓ Connected to Redis")
}

func setupDatabaseTrigger() error {
	trigger := `
	DROP TRIGGER IF EXISTS order_insert_notify ON orders;
	DROP TRIGGER IF EXISTS order_update_notify ON orders;
	DROP TRIGGER IF EXISTS order_remove_notify ON orders;
	DROP FUNCTION IF EXISTS notify_order_insert();
	DROP FUNCTION IF EXISTS notify_order_remove();

	CREATE OR REPLACE FUNCTION notify_order_insert()
	RETURNS TRIGGER AS $$
	BEGIN
		IF NEW.status = 'pending' AND NEW.is_paid = true THEN
			PERFORM pg_notify('new_order',
				json_build_object(
					'id', NEW.id,
					'type', NEW.type,
					'status', NEW.status,
					'price', NEW.price,
					'point', NEW.point,
					'price_from', NEW.price_from,
					'price_to', NEW.price_to,
					'bank_id', NEW.bank_id,
					'loan_type_id', NEW.loan_type_id,
					'percent_id', NEW.percent_id,
					'time_id', NEW.time_id,
					'created_at', to_char(NEW.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')
				)::text
			);
		END IF;
		RETURN NEW;
	END;
	$$ LANGUAGE plpgsql;

	CREATE OR REPLACE FUNCTION notify_order_remove()
	RETURNS TRIGGER AS $$
	BEGIN
		IF OLD.status = 'pending' AND OLD.is_paid = true AND
		   (NEW.status != 'pending' OR NEW.is_paid = false) THEN
			PERFORM pg_notify('remove_order', OLD.id::text);
		END IF;
		RETURN NEW;
	END;
	$$ LANGUAGE plpgsql;

	CREATE TRIGGER order_insert_notify
	AFTER INSERT ON orders
	FOR EACH ROW
	EXECUTE FUNCTION notify_order_insert();

	CREATE TRIGGER order_update_notify
	AFTER UPDATE ON orders
	FOR EACH ROW
	EXECUTE FUNCTION notify_order_insert();

	CREATE TRIGGER order_remove_notify
	AFTER UPDATE ON orders
	FOR EACH ROW
	EXECUTE FUNCTION notify_order_remove();
	`

	_, err := db.Exec(trigger)
	if err != nil {
		return err
	}

	log.Println("✓ Database triggers created (insert + update + remove)")
	return nil
}

func blocklistKey(orderAID, orderBID string) string {
	if orderAID < orderBID {
		return orderAID + "|" + orderBID
	}
	return orderBID + "|" + orderAID
}

func loadBlocklist() error {
	rows, err := db.Query(`SELECT order_a_id, order_b_id FROM order_match_blocklist`)
	if err != nil {
		return err
	}
	defer rows.Close()

	orderBook.mu.Lock()
	defer orderBook.mu.Unlock()

	for rows.Next() {
		var a, b string
		if err := rows.Scan(&a, &b); err != nil {
			return err
		}
		orderBook.Blocklist[blocklistKey(a, b)] = true
	}

	log.Printf("✓ Loaded %d blocklist entries", len(orderBook.Blocklist))
	return nil
}

func expireMatches() {
	rows, err := db.Query(`
		SELECT id, matched_with_id, type
		FROM orders
		WHERE status = 'matched'
		  AND match_expires_at < NOW()
		  AND matched_with_id IS NOT NULL
	`)
	if err != nil {
		log.Printf("✗ expireMatches query failed: %v", err)
		return
	}
	defer rows.Close()

	type expiredEntry struct {
		id            string
		matchedWithID string
		orderType     string
	}

	var expiredList []expiredEntry
	seen := make(map[string]bool)

	for rows.Next() {
		var e expiredEntry
		if err := rows.Scan(&e.id, &e.matchedWithID, &e.orderType); err != nil {
			continue
		}
		key := blocklistKey(e.id, e.matchedWithID)
		if !seen[key] {
			expiredList = append(expiredList, e)
			seen[key] = true
		}
	}

	if len(expiredList) == 0 {
		return
	}

	tx, err := db.Begin()
	if err != nil {
		log.Printf("✗ expireMatches tx begin failed: %v", err)
		return
	}
	defer tx.Rollback()

	resetStmt, err := tx.Prepare(`
		UPDATE orders
		SET status = 'pending',
		    matched_with_id = NULL,
		    matched_at = NULL,
		    match_expires_at = NULL,
		    updated_at = NOW()
		WHERE id = $1
	`)
	if err != nil {
		log.Printf("✗ expireMatches prepare reset failed: %v", err)
		return
	}
	defer resetStmt.Close()

	blockStmt, err := tx.Prepare(`
		INSERT INTO order_match_blocklist (id, order_a_id, order_b_id, created_at, updated_at)
		VALUES ($1, $2, $3, NOW(), NOW())
		ON CONFLICT (order_a_id, order_b_id) DO NOTHING
	`)
	if err != nil {
		log.Printf("✗ expireMatches prepare block failed: %v", err)
		return
	}
	defer blockStmt.Close()

	var resetIDs []string
	newBlockKeys := make(map[string][2]string)

	for _, e := range expiredList {
		a, b := e.id, e.matchedWithID
		if a > b {
			a, b = b, a
		}

		key := blocklistKey(a, b)

		if _, err = blockStmt.Exec(uuid.New().String(), a, b); err != nil {
			log.Printf("✗ blocklist insert failed for %s|%s: %v", a, b, err)
			continue
		}

		if _, err = resetStmt.Exec(e.id); err != nil {
			log.Printf("✗ reset order %s failed: %v", e.id, err)
			continue
		}

		resetIDs = append(resetIDs, e.id)
		newBlockKeys[key] = [2]string{a, b}
	}

	if err := tx.Commit(); err != nil {
		log.Printf("✗ expireMatches commit failed: %v", err)
		return
	}

	orderBook.mu.Lock()
	for key := range newBlockKeys {
		orderBook.Blocklist[key] = true
	}
	orderBook.mu.Unlock()

	if len(resetIDs) > 0 {
		reloadExpiredOrders(resetIDs)
		go tryMatchOrders()
	}

	log.Printf("✓ Expired %d matched pairs → back to pending", len(resetIDs))
}

func reloadExpiredOrders(ids []string) {
	placeholders := make([]string, len(ids))
	args := make([]interface{}, len(ids))
	for i, id := range ids {
		placeholders[i] = fmt.Sprintf("$%d", i+1)
		args[i] = id
	}

	query := fmt.Sprintf(`
		SELECT id, type, status, price, point, price_from, price_to,
		       bank_id, loan_type_id, percent_id, time_id, created_at
		FROM orders
		WHERE id IN (%s)
	`, strings.Join(placeholders, ","))

	rows, err := db.Query(query, args...)
	if err != nil {
		log.Printf("✗ reloadExpiredOrders query failed: %v", err)
		return
	}
	defer rows.Close()

	orderBook.mu.Lock()
	defer orderBook.mu.Unlock()

	count := 0
	for rows.Next() {
		var order Order
		err := rows.Scan(
			&order.ID, &order.Type, &order.Status, &order.Price, &order.Point,
			&order.PriceFrom, &order.PriceTo,
			&order.BankID, &order.LoanTypeID, &order.PercentID,
			&order.TimeID, &order.CreatedAt,
		)
		if err != nil {
			continue
		}

		if order.Type == "buy" {
			orderBook.BuyOrders = append(orderBook.BuyOrders, order)
		} else {
			orderBook.SellOrders = append(orderBook.SellOrders, order)
		}
		count++
	}

	log.Printf("✓ Reloaded %d expired orders back to order book", count)
}

func startExpireWatcher(ctx context.Context) {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	expireMatches()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			expireMatches()
		}
	}
}

func removeOrderFromBook(orderID string) {
	orderBook.mu.Lock()
	defer orderBook.mu.Unlock()

	orderBook.BuyOrders = filterOrderByID(orderBook.BuyOrders, orderID)
	orderBook.SellOrders = filterOrderByID(orderBook.SellOrders, orderID)
}

func filterOrderByID(orders []Order, id string) []Order {
	filtered := make([]Order, 0, len(orders))
	for _, o := range orders {
		if o.ID != id {
			filtered = append(filtered, o)
		}
	}
	return filtered
}

func listenForRemovedOrders(ctx context.Context) {
	reportProblem := func(ev pq.ListenerEventType, err error) {
		if err != nil {
			log.Printf("Remove listener error: %v", err)
		}
	}

	listener := pq.NewListener(
		getDatabaseURL(),
		10*time.Second,
		time.Minute,
		reportProblem,
	)

	if err := listener.Listen("remove_order"); err != nil {
		log.Fatal("Failed to listen on remove_order:", err)
	}

	log.Println("✓ Listening for removed orders...")

	for {
		select {
		case <-ctx.Done():
			listener.Close()
			return
		case notification := <-listener.Notify:
			if notification == nil {
				continue
			}

			orderID := strings.TrimSpace(notification.Extra)
			if orderID == "" {
				continue
			}

			removeOrderFromBook(orderID)
			log.Printf("✗ Order %s removed from order book (status changed)", orderID[:8])
		}
	}
}

func listenForNewOrders(ctx context.Context) {
	reportProblem := func(ev pq.ListenerEventType, err error) {
		if err != nil {
			log.Printf("Listener error: %v", err)
		}
	}

	listener := pq.NewListener(
		getDatabaseURL(),
		10*time.Second,
		time.Minute,
		reportProblem,
	)

	if err := listener.Listen("new_order"); err != nil {
		log.Fatal("Failed to listen:", err)
	}

	log.Println("✓ Listening for new orders...")

	for {
		select {
		case <-ctx.Done():
			listener.Close()
			return
		case notification := <-listener.Notify:
			if notification == nil {
				continue
			}

			var rawOrder map[string]interface{}
			if err := json.Unmarshal([]byte(notification.Extra), &rawOrder); err != nil {
				log.Printf("Failed to parse notification: %v", err)
				continue
			}

			if createdAtStr, ok := rawOrder["created_at"].(string); ok {
				if !strings.HasSuffix(createdAtStr, "Z") && !strings.Contains(createdAtStr, "+") {
					rawOrder["created_at"] = createdAtStr + "Z"
				}
			}

			fixedJSON, _ := json.Marshal(rawOrder)
			var order Order
			if err := json.Unmarshal(fixedJSON, &order); err != nil {
				log.Printf("Failed to parse order: %v", err)
				continue
			}

			log.Printf("→ New/Updated %s order: %s @ %dM (range: %v-%v)",
				order.Type, order.ID[:8], order.Price/1000000,
				formatPrice(order.PriceFrom), formatPrice(order.PriceTo))

			removeOrderFromBook(order.ID)
			addOrderToBook(order)
			tryMatchOrders()
		}
	}
}

func formatPrice(price *int64) string {
	if price == nil {
		return "nil"
	}
	return fmt.Sprintf("%dM", *price/1000000)
}

func addOrderToBook(order Order) {
	orderBook.mu.Lock()
	defer orderBook.mu.Unlock()

	if order.Type == "buy" {
		orderBook.BuyOrders = append(orderBook.BuyOrders, order)
	} else {
		orderBook.SellOrders = append(orderBook.SellOrders, order)
	}
}

func loadPendingOrders() error {
	query := `
		SELECT id, type, status, price, point, price_from, price_to,
		       bank_id, loan_type_id, percent_id, time_id, created_at
		FROM orders
		WHERE status = 'pending' AND is_paid = true
		ORDER BY created_at ASC
	`

	rows, err := db.Query(query)
	if err != nil {
		return err
	}
	defer rows.Close()

	orderBook.mu.Lock()
	defer orderBook.mu.Unlock()

	for rows.Next() {
		var order Order
		err := rows.Scan(
			&order.ID, &order.Type, &order.Status, &order.Price, &order.Point,
			&order.PriceFrom, &order.PriceTo,
			&order.BankID, &order.LoanTypeID, &order.PercentID,
			&order.TimeID, &order.CreatedAt,
		)
		if err != nil {
			return err
		}

		if order.Type == "buy" {
			orderBook.BuyOrders = append(orderBook.BuyOrders, order)
		} else {
			orderBook.SellOrders = append(orderBook.SellOrders, order)
		}
	}

	log.Printf("✓ Loaded %d buy orders, %d sell orders",
		len(orderBook.BuyOrders), len(orderBook.SellOrders))

	return nil
}

func tryMatchOrders() {
	orderBook.mu.Lock()
	defer orderBook.mu.Unlock()

	matchedBuyIDs := make(map[string]bool)
	matchedSellIDs := make(map[string]bool)
	matches := make([]MatchEvent, 0)

	for i := range orderBook.BuyOrders {
		if matchedBuyIDs[orderBook.BuyOrders[i].ID] {
			continue
		}

		for j := range orderBook.SellOrders {
			if matchedSellIDs[orderBook.SellOrders[j].ID] {
				continue
			}

			buy := &orderBook.BuyOrders[i]
			sell := &orderBook.SellOrders[j]

			if canMatch(buy, sell) {
				matchPrice := calculateMatchPrice(buy, sell)

				match := Match{
					ID:           uuid.New().String(),
					BuyOrderID:   buy.ID,
					SellOrderID:  sell.ID,
					MatchedPrice: matchPrice,
					MatchedAt:    time.Now(),
				}

				matchedBuyIDs[buy.ID] = true
				matchedSellIDs[sell.ID] = true

				matches = append(matches, MatchEvent{
					Match:     match,
					BuyOrder:  *buy,
					SellOrder: *sell,
					EventType: "matched",
				})

				log.Printf("✓ Match: Buy %s <-> Sell %s @ %dM",
					buy.ID[:8], sell.ID[:8], matchPrice/1000000)

				break
			}
		}
	}

	if len(matches) > 0 {
		if err := batchInsertMatches(matches); err != nil {
			log.Printf("✗ Failed to insert matches: %v", err)
			return
		}

		orderBook.BuyOrders = filterOrders(orderBook.BuyOrders, matchedBuyIDs)
		orderBook.SellOrders = filterOrders(orderBook.SellOrders, matchedSellIDs)

		for _, match := range matches {
			select {
			case eventQueue <- match:
			default:
				log.Println("⚠ Event queue full, dropping event")
			}
		}

		broadcastMatches(matches)
	}
}

func canMatch(buy, sell *Order) bool {
	if buy.BankID != sell.BankID ||
		buy.LoanTypeID != sell.LoanTypeID ||
		buy.PercentID != sell.PercentID ||
		buy.TimeID != sell.TimeID ||
		buy.Point != sell.Point {
		return false
	}

	if orderBook.Blocklist[blocklistKey(buy.ID, sell.ID)] {
		return false
	}

	buyFrom := buy.PriceFrom
	buyTo := buy.PriceTo
	sellFrom := sell.PriceFrom
	sellTo := sell.PriceTo

	if buyFrom == nil {
		buyFrom = &buy.Price
	}
	if buyTo == nil {
		buyTo = &buy.Price
	}
	if sellFrom == nil {
		sellFrom = &sell.Price
	}
	if sellTo == nil {
		sellTo = &sell.Price
	}

	return *buyTo >= *sellFrom && *buyFrom <= *sellTo
}

func calculateMatchPrice(buy, sell *Order) int64 {
	buyMax := buy.PriceTo
	if buyMax == nil {
		buyMax = &buy.Price
	}

	sellMin := sell.PriceFrom
	if sellMin == nil {
		sellMin = &sell.Price
	}

	return (*buyMax + *sellMin) / 2
}

func filterOrders(orders []Order, matchedIDs map[string]bool) []Order {
	filtered := make([]Order, 0, len(orders))
	for _, order := range orders {
		if !matchedIDs[order.ID] {
			filtered = append(filtered, order)
		}
	}
	return filtered
}

func batchInsertMatches(events []MatchEvent) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	matchStmt, err := tx.Prepare(`
		INSERT INTO order_matches
		(id, buy_order_id, sell_order_id, matched_price, matched_at, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`)
	if err != nil {
		return err
	}
	defer matchStmt.Close()

	orderStmt, err := tx.Prepare(`
		UPDATE orders
		SET status = 'matched',
		    matched_with_id = $1,
		    matched_at = $2,
		    match_expires_at = $3,
		    updated_at = $4
		WHERE id = $5
	`)
	if err != nil {
		return err
	}
	defer orderStmt.Close()

	now := time.Now()
	expiresAt := now.Add(72 * time.Hour)

	for _, event := range events {
		match := event.Match

		_, err = matchStmt.Exec(
			match.ID,
			match.BuyOrderID,
			match.SellOrderID,
			match.MatchedPrice,
			match.MatchedAt,
			now,
			now,
		)
		if err != nil {
			return err
		}

		_, err = orderStmt.Exec(
			match.SellOrderID,
			match.MatchedAt,
			expiresAt,
			now,
			match.BuyOrderID,
		)
		if err != nil {
			return err
		}

		_, err = orderStmt.Exec(
			match.BuyOrderID,
			match.MatchedAt,
			expiresAt,
			now,
			match.SellOrderID,
		)
		if err != nil {
			return err
		}
	}

	if err := tx.Commit(); err != nil {
		return err
	}

	for _, event := range events {
		match := event.Match

		if err := SendMatchNotificationMessage(db, match.BuyOrderID, match.SellOrderID); err != nil {
			log.Printf("⚠️ خطا در ارسال پیام خودکار: %v", err)
		}
	}

	return nil
}

func processMatchEvents(ctx context.Context) {
	log.Println("✓ Event processor started")

	for {
		select {
		case <-ctx.Done():
			return
		case event := <-eventQueue:
			log.Printf("📧 Event: Match %s - Buy: %s, Sell: %s",
				event.Match.ID[:8],
				event.BuyOrder.ID[:8],
				event.SellOrder.ID[:8])
		}
	}
}

func broadcastMatches(events []MatchEvent) {
	ctx := context.Background()

	for _, event := range events {
		matchData := map[string]interface{}{
			"type":          "match",
			"buy_order_id":  event.Match.BuyOrderID,
			"sell_order_id": event.Match.SellOrderID,
			"matched_price": event.Match.MatchedPrice,
			"match_id":      event.Match.ID,
			"matched_at":    event.Match.MatchedAt,
		}

		payload, _ := json.Marshal(matchData)
		if err := redisClient.Publish(ctx, "order_matches", string(payload)).Err(); err != nil {
			log.Printf("❌ Failed to publish match to Redis: %v", err)
		} else {
			log.Printf("✓ Match %s published to Redis → port 8080", event.Match.ID[:8])
		}
	}

	data := map[string]interface{}{
		"type":      "matches",
		"matches":   events,
		"timestamp": time.Now(),
	}

	message, _ := json.Marshal(data)

	clientsMu.RLock()
	for client := range clients {
		err := client.WriteMessage(websocket.TextMessage, message)
		if err != nil {
			log.Printf("Broadcast error: %v", err)
			client.Close()
			delete(clients, client)
		}
	}
	clientsMu.RUnlock()
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println("Upgrade error:", err)
		return
	}

	clientsMu.Lock()
	clients[conn] = true
	clientsMu.Unlock()

	log.Printf("Client connected. Total: %d", len(clients))

	orderBook.mu.RLock()
	initialData := map[string]interface{}{
		"type":        "initial",
		"buy_orders":  orderBook.BuyOrders,
		"sell_orders": orderBook.SellOrders,
	}
	orderBook.mu.RUnlock()

	conn.WriteJSON(initialData)

	for {
		_, _, err := conn.ReadMessage()
		if err != nil {
			clientsMu.Lock()
			delete(clients, conn)
			clientsMu.Unlock()
			conn.Close()
			log.Printf("Client disconnected. Total: %d", len(clients))
			break
		}
	}
}

func main() {
	dbURL := getDatabaseURL()

	var err error
	db, err = sql.Open("postgres", dbURL)
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer db.Close()

	db.SetMaxOpenConns(25)
	db.SetMaxIdleConns(10)
	db.SetConnMaxLifetime(5 * time.Minute)

	if err := db.Ping(); err != nil {
		log.Fatal("Database ping failed:", err)
	}

	log.Println("✓ Connected to database")

	initRedis()

	if err := setupDatabaseTrigger(); err != nil {
		log.Fatal("Failed to setup trigger:", err)
	}

	if err := loadBlocklist(); err != nil {
		log.Fatal("Failed to load blocklist:", err)
	}

	if err := loadPendingOrders(); err != nil {
		log.Fatal("Failed to load orders:", err)
	}

	tryMatchOrders()

	ctx := context.Background()

	go listenForNewOrders(ctx)
	go listenForRemovedOrders(ctx)
	go processMatchEvents(ctx)
	go startExpireWatcher(ctx)

	http.HandleFunc("/ws/matching", handleWebSocket)
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})

	port := getEnv("PORT", "8082")
	log.Printf("✓ Matching Engine running on :%s", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

func SendMatchNotificationMessage(db *sql.DB, buyOrderID, sellOrderID string) error {
	var buyerCustomerID, sellerCustomerID string

	err := db.QueryRow(`SELECT customer_id FROM orders WHERE id = $1`, buyOrderID).Scan(&buyerCustomerID)
	if err != nil {
		return fmt.Errorf("failed to get buyer customer_id: %w", err)
	}

	err = db.QueryRow(`SELECT customer_id FROM orders WHERE id = $1`, sellOrderID).Scan(&sellerCustomerID)
	if err != nil {
		return fmt.Errorf("failed to get seller customer_id: %w", err)
	}

	message := map[string]interface{}{
		"receiver_id": sellerCustomerID,
		"content":     "سلام، سفارش شما با درخواست من مچ شد. من شرایط لازم برای دریافت وام را دارم و آماده هستم تا مراحل بعدی را با هم طی کنیم. لطفاً در اسرع وقت با من تماس بگیرید.",
		"type":        "text",
	}

	jsonData, err := json.Marshal(message)
	if err != nil {
		return fmt.Errorf("failed to marshal message: %w", err)
	}

	chatServiceURL := fmt.Sprintf("http://websocket:8080/chat/send?sender_id=%s", buyerCustomerID)
	req, err := http.NewRequest("POST", chatServiceURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send message: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("chat service returned status: %d, body: %s", resp.StatusCode, string(body))
	}

	log.Printf("✅ پیام خودکار به فروشنده %s ارسال شد", sellerCustomerID)
	return nil
}