package fundmarket

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gothinkster/golang-gin-realworld-example-app/common"
	"github.com/gothinkster/golang-gin-realworld-example-app/models"
)

// GetProducts returns all fund market products
func GetProducts(c *gin.Context) {
	db := common.GetDB()

	// Query assets with their performance data
	// token_list=1: return all active assets (for dropdowns)
	// default: exclude stablecoins (Fund Market product listing)
	var assets []models.Asset
	query := db.Where("is_active = ?", true)
	if c.Query("token_list") != "1" {
		query = query.Where("token_role != ?", "stablecoin")
	}
	err := query.Find(&assets).Error
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   "Failed to fetch products",
		})
		return
	}

	// Get performance data for each asset
	products := make([]models.ProductResponse, 0)
	for _, asset := range assets {
		var perf models.AssetPerformance
		db.Where("asset_id = ?", asset.ID).
			Order("snapshot_date DESC").
			First(&perf)

		riskLabel := asset.RiskLabel
		if riskLabel == "" {
			riskLabel = "--"
		}

		circulatingSupplyStr := "--"
		if perf.CirculatingSupply > 0 {
			circulatingSupplyStr = formatUSD(perf.CirculatingSupply)
		}

		poolCapStr := "--"
		if asset.PoolCapUSD > 0 {
			poolCapStr = formatUSD(asset.PoolCapUSD)
		}

		product := models.ProductResponse{
			ID:                  int(asset.ID),
			Name:                asset.Name,
			TokenSymbol:         asset.TokenSymbol,
			Decimals:            asset.Decimals,
			ContractAddress:     asset.ContractAddress,
			ChainID:             asset.ChainID,
			TokenRole:           asset.TokenRole,
			Category:            asset.Category,
			CategoryColor:       asset.CategoryColor,
			IconURL:             asset.IconURL,
			YieldAPY:            fmt.Sprintf("%.1f%%", asset.TargetAPY),
			PoolCap:             poolCapStr,
			Risk:                riskLabel,
			RiskLevel:           asset.RiskLevel,
			CirculatingSupply:   circulatingSupplyStr,
			PoolCapacityPercent: perf.PoolCapacityPercent,
		}
		products = append(products, product)
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    products,
	})
}

// GetStats returns fund market statistics
func GetStats(c *gin.Context) {
	db := common.GetDB()

	// Total Value Locked = sum of latest tvlusd across all active assets
	var totalTVL float64
	db.Raw(`
		SELECT COALESCE(SUM(ap.tvlusd), 0)
		FROM asset_performance ap
		INNER JOIN (
			SELECT asset_id, MAX(snapshot_date) AS latest
			FROM asset_performance
			GROUP BY asset_id
		) latest ON ap.asset_id = latest.asset_id AND ap.snapshot_date = latest.latest
		INNER JOIN assets a ON a.id = ap.asset_id AND a.is_active = true
	`).Scan(&totalTVL)

	// Cumulative Yield = sum of cumulative_yield_usd from latest snapshots
	var totalYield float64
	db.Raw(`
		SELECT COALESCE(SUM(ap.cumulative_yield_usd), 0)
		FROM asset_performance ap
		INNER JOIN (
			SELECT asset_id, MAX(snapshot_date) AS latest
			FROM asset_performance
			GROUP BY asset_id
		) latest ON ap.asset_id = latest.asset_id AND ap.snapshot_date = latest.latest
	`).Scan(&totalYield)

	stats := []models.StatsResponse{
		{
			Label:      "Total Value Locked",
			Value:      formatUSD(totalTVL),
			Change:     "",
			IsPositive: true,
		},
		{
			Label:      "Cumulative Yield",
			Value:      formatUSD(totalYield),
			Change:     "",
			IsPositive: true,
		},
		{
			Label:      "Your Total Balance",
			Value:      "--",
			Change:     "",
			IsPositive: true,
		},
		{
			Label:      "Your Total Earning",
			Value:      "--",
			Change:     "",
			IsPositive: true,
		},
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    stats,
	})
}

// GetProductByID returns detailed product information by ID
func GetProductByID(c *gin.Context) {
	db := common.GetDB()
	id := c.Param("id")

	var asset models.Asset
	err := db.Where("id = ? AND is_active = ?", id, true).First(&asset).Error
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"success": false,
			"error":   "Product not found",
		})
		return
	}

	// Get performance data
	var perf models.AssetPerformance
	db.Where("asset_id = ?", asset.ID).
		Order("snapshot_date DESC").
		First(&perf)

	// Calculate volume change vs 7-day average (excluding today)
	var avg7dVolume float64
	db.Raw(`
		SELECT AVG(volume_24h_usd) FROM (
			SELECT volume_24h_usd FROM asset_performance
			WHERE asset_id = ? AND snapshot_date < CURDATE()
			ORDER BY snapshot_date DESC
			LIMIT 7
		) t
	`, asset.ID).Scan(&avg7dVolume)
	volumeChangeVsAvg := 0.0
	if avg7dVolume > 0 {
		volumeChangeVsAvg = (perf.Volume24hUSD - avg7dVolume) / avg7dVolume * 100
	}

	// Get custody info
	var custody models.AssetCustody
	hasCustody := db.Where("asset_id = ?", asset.ID).First(&custody).Error == nil

	// Get audit reports
	var auditReports []models.AssetAuditReport
	db.Where("asset_id = ? AND is_active = ?", asset.ID, true).
		Order("report_date DESC").
		Limit(10).
		Find(&auditReports)

	// Get product links
	var productLinks []models.ProductLink
	db.Where("asset_id = ? AND is_active = ?", asset.ID, true).
		Order("display_order ASC, id ASC").
		Find(&productLinks)

	// Build response
	detail := models.ProductDetailResponse{
		// Basic Info
		ID:          int(asset.ID),
		AssetCode:   asset.AssetCode,
		Name:        asset.Name,
		Subtitle:    asset.Subtitle,
		Description: asset.Description,
		TokenSymbol: asset.TokenSymbol,
		Decimals:    asset.Decimals,

		// Investment Parameters
		UnderlyingAssets:  asset.UnderlyingAssets,
		PoolCapUSD:        asset.PoolCapUSD,
		RiskLevel:         asset.RiskLevel,
		RiskLabel:         asset.RiskLabel,
		TargetAPY:         asset.TargetAPY,

		// Contract Info
		ContractAddress: asset.ContractAddress,
		ChainID:         asset.ChainID,

		// Display Info
		Category:      asset.Category,
		CategoryColor: asset.CategoryColor,
		IconURL:       asset.IconURL,

		// Performance Data
		CurrentAPY:           perf.CurrentAPY,
		TVLUSD:               perf.TVLUSD,
		Volume24hUSD:         perf.Volume24hUSD,
		VolumeChangeVsAvg:    volumeChangeVsAvg,
		CirculatingSupply:    perf.CirculatingSupply,
		PoolCapacityPercent:  perf.PoolCapacityPercent,
		CurrentPrice:         perf.YTPrice,
	}

	// Add custody info if exists
	if hasCustody {
		lastAuditDate := ""
		if custody.LastAuditDate.Time != nil {
			lastAuditDate = custody.LastAuditDate.Time.Format("02 Jan 2006")
		}

		var additionalInfo map[string]interface{}
		if custody.AdditionalInfo != "" {
			_ = json.Unmarshal([]byte(custody.AdditionalInfo), &additionalInfo)
		}
		if additionalInfo == nil {
			additionalInfo = map[string]interface{}{}
		}
		// 从 maturity_date 动态计算 days_remaining，不再依赖存储值
		if maturityStr, ok := additionalInfo["maturity_date"].(string); ok && maturityStr != "" {
			if maturityDate, err := time.Parse("2006-01-02", maturityStr); err == nil {
				days := int(time.Until(maturityDate).Hours() / 24)
				additionalInfo["days_remaining"] = days
			}
		} else {
			delete(additionalInfo, "days_remaining")
		}

		detail.Custody = &models.CustodyInfo{
			CustodianName:   custody.CustodianName,
			CustodyType:     custody.CustodyType,
			CustodyLocation: custody.CustodyLocation,
			AuditorName:     custody.AuditorName,
			LastAuditDate:   lastAuditDate,
			AuditReportURL:  custody.AuditReportURL,
			AdditionalInfo:  additionalInfo,
		}
	}

	// Add audit reports
	detail.AuditReports = make([]models.AuditReportInfo, 0, len(auditReports))
	for _, report := range auditReports {
		detail.AuditReports = append(detail.AuditReports, models.AuditReportInfo{
			ReportType:  report.ReportType,
			ReportTitle: report.ReportTitle,
			ReportDate:  report.ReportDate.Format("02 Jan 2006"),
			AuditorName: report.AuditorName,
			Summary:     report.Summary,
			ReportURL:   report.ReportURL,
		})
	}

	// Add product links
	detail.ProductLinks = make([]models.ProductLinkInfo, 0, len(productLinks))
	for _, link := range productLinks {
		detail.ProductLinks = append(detail.ProductLinks, models.ProductLinkInfo{
			LinkText:     link.LinkText,
			LinkURL:      link.LinkURL,
			Description:  link.Description,
			DisplayArea:  link.DisplayArea,
			DisplayOrder: link.DisplayOrder,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    detail,
	})
}

// GetProductHistory returns daily APY/price history for a product (one point per day)
func GetProductHistory(c *gin.Context) {
	db := common.GetDB()
	id := c.Param("id")

	// Pick the last snapshot of each calendar day (UTC), up to 30 days
	var snapshots []models.APYSnapshot
	if err := db.Raw(`
		SELECT ap.* FROM apy_snapshots ap
		INNER JOIN (
			SELECT DATE(snapshot_time) AS snap_date, MAX(snapshot_time) AS latest
			FROM apy_snapshots
			WHERE asset_id = ?
			GROUP BY DATE(snapshot_time)
			ORDER BY snap_date DESC
			LIMIT 30
		) d ON DATE(ap.snapshot_time) = d.snap_date AND ap.snapshot_time = d.latest
		WHERE ap.asset_id = ?
		ORDER BY ap.snapshot_time ASC
	`, id, id).Scan(&snapshots).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   "Failed to fetch history",
		})
		return
	}

	type HistoryPoint struct {
		Time  string  `json:"time"`
		APY   float64 `json:"apy"`
		Price float64 `json:"price"`
	}

	points := make([]HistoryPoint, len(snapshots))
	for i, s := range snapshots {
		points[i] = HistoryPoint{
			Time:  s.SnapshotTime.UTC().Format(time.RFC3339),
			APY:   s.APYValue,
			Price: s.Price,
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    points,
	})
}

// Helper function to format USD values
func formatUSD(value float64) string {
	if value >= 1000000 {
		return fmt.Sprintf("$%.1fM", value/1000000)
	} else if value >= 1000 {
		return fmt.Sprintf("$%.1fK", value/1000)
	}
	return fmt.Sprintf("$%.2f", value)
}

// GetDailyReturns returns daily net return data for a product by month.
// Query params: year (int), month (int). Defaults to current month.
func GetDailyReturns(c *gin.Context) {
	db := common.GetDB()
	id := c.Param("id")

	now := time.Now().UTC()
	year := now.Year()
	month := int(now.Month())

	if y, err := strconv.Atoi(c.Query("year")); err == nil {
		year = y
	}
	if m, err := strconv.Atoi(c.Query("month")); err == nil && m >= 1 && m <= 12 {
		month = m
	}

	startDate := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
	endDate := startDate.AddDate(0, 1, 0)
	// Include one day before month start for delta calculation
	queryStart := startDate.AddDate(0, 0, -1)

	var perfs []models.AssetPerformance
	db.Where("asset_id = ? AND snapshot_date >= ? AND snapshot_date < ?", id, queryStart, endDate).
		Order("snapshot_date ASC").
		Find(&perfs)

	// Map date string -> performance
	type perfEntry struct {
		ytPrice float64
		hasData bool
	}
	perfMap := make(map[string]perfEntry)
	for _, p := range perfs {
		perfMap[p.SnapshotDate.Format("2006-01-02")] = perfEntry{ytPrice: p.YTPrice, hasData: true}
	}

	type DayReturn struct {
		Date        string  `json:"date"`
		YTPrice     float64 `json:"ytPrice"`
		DailyReturn float64 `json:"dailyReturn"`
		HasData     bool    `json:"hasData"`
	}

	today := now.Format("2006-01-02")
	var results []DayReturn
	for d := startDate; d.Before(endDate); d = d.AddDate(0, 0, 1) {
		dateStr := d.Format("2006-01-02")
		// Skip future days
		if dateStr > today {
			break
		}
		cur := perfMap[dateStr]
		prev := perfMap[d.AddDate(0, 0, -1).Format("2006-01-02")]

		dailyReturn := 0.0
		if cur.hasData && prev.hasData && prev.ytPrice > 0 {
			dailyReturn = (cur.ytPrice - prev.ytPrice) / prev.ytPrice * 100
		}

		results = append(results, DayReturn{
			Date:        dateStr,
			YTPrice:     cur.ytPrice,
			DailyReturn: dailyReturn,
			HasData:     cur.hasData,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    results,
	})
}
