// cmd/loan-generator/main.go
package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"time"

	"github.com/google/uuid"
	_ "github.com/lib/pq"
)

type Order struct {
	ID         string    `json:"id"`
	Type       string    `json:"type"`
	BankID     string    `json:"bank_id"`
	BankName   string    `json:"bank_name"`
	BankLogo   string    `json:"bank_logo"`
	LoanTypeID string    `json:"loan_type_id"`
	LoanType   string    `json:"loan_type"`
	PercentID  string    `json:"percent_id"`
	Percent    string    `json:"percent"`
	TimeID     string    `json:"time_id"`
	Time       string    `json:"time"`
	Price      int64     `json:"price"`
	PriceFrom  *int64    `json:"price_from,omitempty"`
	PriceTo    *int64    `json:"price_to,omitempty"`
	Status     string    `json:"status"`
	CreatedAt  time.Time `json:"created_at"`
}

type BroadcastMessage struct {
	Type  string `json:"type"`
	Order Order  `json:"order"`
}

type ReferenceData struct {
	BankID     string
	BankName   string
	BankLogo   string
	LoanTypeID string
	LoanType   string
	PercentID  string
	Percent    string
	TimeID     string
	Time       string
}

var (
	db            *sql.DB
	referenceData []ReferenceData
	wsURL         string
)

var basePrices = []int64{
	30_000_000,
	35_000_000,
	40_000_000,
	50_000_000,
	60_000_000,
	70_000_000,
	80_000_000,
	90_000_000,
	100_000_000,
	120_000_000,
	125_000_000,
	150_000_000,
	200_000_000,
	250_000_000,
	300_000_000,
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {
	dbHost := getEnv("DB_HOST", "localhost")
	dbPort := getEnv("DB_PORT", "5432")
	dbUser := getEnv("DB_USER", "postgres")
	dbPass := getEnv("DB_PASSWORD", "postgres")
	dbName := getEnv("DB_NAME", "loan_db")
	wsURL = getEnv("WS_URL", "http://localhost:8080/broadcast")

	connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
		dbHost, dbPort, dbUser, dbPass, dbName)

	var err error
	db, err = sql.Open("postgres", connStr)
	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 PostgreSQL")

	if err := loadReferenceData(); err != nil {
		log.Fatal("❌ Failed to load reference data:", err)
	}

	log.Printf("✅ Loaded %d reference combinations\n", len(referenceData))

	ticker := time.NewTicker(2 * time.Second)
	defer ticker.Stop()

	log.Println("🚀 Loan generator started - generating orders every 2 seconds")

	for range ticker.C {
		if err := generateAndInsertOrder(); err != nil {
			log.Printf("✗ Failed to generate order: %v\n", err)
		}
	}
}

func loadReferenceData() error {
	query := `
		SELECT
			b.id, b.name, COALESCE(b.logo, ''),
			lt.id, lt.name,
			p.id, p.percent,
			t.id, t.time
		FROM banks b
		CROSS JOIN loan_types lt
		CROSS JOIN percents p
		CROSS JOIN loan_times t
		WHERE b.id IS NOT NULL
		  AND lt.id IS NOT NULL
		  AND p.id IS NOT NULL
		  AND t.id IS NOT NULL
		LIMIT 1000
	`

	rows, err := db.Query(query)
	if err != nil {
		return fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	referenceData = make([]ReferenceData, 0, 1000)

	for rows.Next() {
		var rd ReferenceData
		if err := rows.Scan(
			&rd.BankID, &rd.BankName, &rd.BankLogo,
			&rd.LoanTypeID, &rd.LoanType,
			&rd.PercentID, &rd.Percent,
			&rd.TimeID, &rd.Time,
		); err != nil {
			return fmt.Errorf("scan failed: %w", err)
		}
		referenceData = append(referenceData, rd)
	}

	if len(referenceData) == 0 {
		return fmt.Errorf("no reference data found")
	}

	return rows.Err()
}

func generateAndInsertOrder() error {
	if len(referenceData) == 0 {
		return fmt.Errorf("no reference data available")
	}

	rd := referenceData[rand.Intn(len(referenceData))]
	orderType := "buy"
	if rand.Float32() < 0.5 {
		orderType = "sell"
	}

	basePrice := basePrices[rand.Intn(len(basePrices))]

	var priceFrom, priceTo *int64

	if orderType == "buy" {
		variance := int64(float64(basePrice) * (0.03 + rand.Float64()*0.07))
		from := basePrice - variance
		priceFrom = &from
		priceTo = &basePrice
	} else {
		variance := int64(float64(basePrice) * (0.03 + rand.Float64()*0.07))
		to := basePrice + variance
		priceFrom = &basePrice
		priceTo = &to
	}

	order := Order{
		ID:         uuid.New().String(),
		Type:       orderType,
		BankID:     rd.BankID,
		BankName:   rd.BankName,
		BankLogo:   rd.BankLogo,
		LoanTypeID: rd.LoanTypeID,
		LoanType:   rd.LoanType,
		PercentID:  rd.PercentID,
		Percent:    rd.Percent,
		TimeID:     rd.TimeID,
		Time:       rd.Time,
		Price:      basePrice,
		PriceFrom:  priceFrom,
		PriceTo:    priceTo,
		Status:     "pending",
		CreatedAt:  time.Now(),
	}

	if err := insertOrder(order); err != nil {
		return err
	}

	log.Printf("✓ Generated %s order: %s - %s - %s - Price: %d (Range: %d - %d)\n",
		orderType, rd.BankName, rd.LoanType, rd.Percent, basePrice, *priceFrom, *priceTo)


	return nil
}

func insertOrder(order Order) error {
	query := `
		INSERT INTO orders (
			id, bank_id, loan_type_id, percent_id, time_id,
			price, price_from, price_to, type, status, created_at
		)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
	`

	_, err := db.Exec(query,
		order.ID,
		order.BankID,
		order.LoanTypeID,
		order.PercentID,
		order.TimeID,
		order.Price,
		order.PriceFrom,
		order.PriceTo,
		order.Type,
		order.Status,
		order.CreatedAt,
	)

	return err
}

func broadcastOrder(order Order) {
	msg := BroadcastMessage{
		Type:  order.Type + "_order",
		Order: order,
	}

	jsonData, err := json.Marshal(msg)
	if err != nil {
		log.Printf("✗ JSON marshal failed: %v\n", err)
		return
	}

	resp, err := http.Post(wsURL, "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		log.Printf("✗ Broadcast failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Printf("✗ Broadcast returned status: %d\n", resp.StatusCode)
		return
	}

	log.Printf("✓ Broadcasted %s order: %s\n", order.Type, order.ID[:8])
}

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