package control

import (
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

var billMonthPattern = regexp.MustCompile(`^\d{4}-(0[1-9]|1[0-2])$`)

type API struct {
	store               *Store
	engine              *Engine
	runtimeMode         string
	mutationToken       string
	writeActionsEnabled bool
	observer            *LiveObserver
}

func NewAPI(store *Store, engine *Engine, runtimeMode, mutationToken string, writeActionsEnabled bool) *API {
	writeActionsEnabled = writeActionsEnabled && runtimeMode == "demo"
	return &API{store: store, engine: engine, runtimeMode: runtimeMode, mutationToken: mutationToken, writeActionsEnabled: writeActionsEnabled}
}

func (a *API) SetObserver(observer *LiveObserver) {
	a.observer = observer
}

func (a *API) Register(router *gin.Engine) {
	group := router.Group("/api/v1")
	group.GET("/runtime", a.runtime)
	group.GET("/close-observations/current", a.currentCloseObservation)
	group.GET("/closes", a.listCloses)
	group.GET("/closes/:closeID", a.getClose)
	group.GET("/closes/:closeID/attempts", a.listAttempts)
	group.GET("/assets", a.listAssets)
	group.POST("/closes", a.authorizeMutation(), a.createClose)
	group.POST("/closes/:closeID/steps/:stepKey/actions", a.authorizeMutation(), a.runAction)
}

func (a *API) runtime(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"runtime_mode":             a.runtimeMode,
		"live_observation_enabled": a.observer != nil,
		"write_actions_enabled":    a.writeActionsEnabled,
		"default_bill_month":       previousBillMonth(),
		"old_cloud_run_dependency": false,
		"source_sha":               envOrValue("FINANCE_PORTAL_BUILD_SHA", "unversioned-local"),
		"migrated_steps": []string{
			"run_monthly_cloud_billing",
			"run_cloud_breakdown",
			"sync_order_to_crm",
			"finalize_revrec",
			"finalize_collection",
		},
	})
}

func (a *API) listCloses(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"items": a.store.ListCloses()})
}

func (a *API) getClose(c *gin.Context) {
	closeRun, ok := a.store.GetClose(c.Param("closeID"))
	if !ok {
		writeAPIError(c, http.StatusNotFound, "close_not_found", "close not found", nil)
		return
	}
	c.JSON(http.StatusOK, closeRun)
}

func (a *API) listAttempts(c *gin.Context) {
	if _, ok := a.store.GetClose(c.Param("closeID")); !ok {
		writeAPIError(c, http.StatusNotFound, "close_not_found", "close not found", nil)
		return
	}
	c.JSON(http.StatusOK, gin.H{"items": a.store.Attempts(c.Param("closeID"))})
}

func (a *API) createClose(c *gin.Context) {
	var request struct {
		BillMonth string `json:"bill_month" binding:"required"`
		Mode      string `json:"mode" binding:"required"`
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		writeAPIError(c, http.StatusBadRequest, "invalid_request", "bill_month and mode are required", nil)
		return
	}
	if !billMonthPattern.MatchString(request.BillMonth) || (request.Mode != "predicted" && request.Mode != "actual") {
		writeAPIError(c, http.StatusUnprocessableEntity, "invalid_close_scope", "bill_month or mode is invalid", nil)
		return
	}
	closeRun, err := a.store.CreateClose(request.BillMonth, request.Mode)
	if err != nil {
		writeAPIError(c, http.StatusConflict, "active_close_exists", err.Error(), nil)
		return
	}
	c.JSON(http.StatusCreated, closeRun)
}

func (a *API) runAction(c *gin.Context) {
	var request ActionRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		writeAPIError(c, http.StatusBadRequest, "invalid_request", "action and reason are required", nil)
		return
	}
	request.Reason = strings.TrimSpace(request.Reason)
	if len(request.Reason) < 4 || len(request.Reason) > 500 {
		writeAPIError(c, http.StatusUnprocessableEntity, "invalid_action_reason", "reason must contain 4 to 500 characters", nil)
		return
	}
	attempt, err := a.engine.Execute(c.Param("closeID"), c.Param("stepKey"), request)
	if err != nil {
		writeAPIError(c, http.StatusConflict, "step_action_conflict", err.Error(), nil)
		return
	}
	c.JSON(http.StatusAccepted, attempt)
}

func (a *API) currentCloseObservation(c *gin.Context) {
	if a.observer == nil {
		writeAPIError(c, http.StatusServiceUnavailable, "live_observer_unavailable", "live observation is not configured", nil)
		return
	}
	billMonth := strings.TrimSpace(c.Query("bill_month"))
	if billMonth == "" {
		billMonth = previousBillMonth()
	}
	mode := strings.TrimSpace(c.Query("mode"))
	if mode == "" {
		mode = "predicted"
	}
	if !billMonthPattern.MatchString(billMonth) || (mode != "predicted" && mode != "actual") {
		writeAPIError(c, http.StatusUnprocessableEntity, "invalid_observation_scope", "bill_month must be YYYY-MM and mode must be predicted or actual", nil)
		return
	}
	observation, ok := a.observer.Cached(billMonth, mode)
	if !ok {
		writeAPIError(c, http.StatusServiceUnavailable, "observation_not_collected", "该账期尚无后台观察；不会在页面请求中触发 provider 查询", nil)
		return
	}
	c.Header("Cache-Control", "no-store")
	c.JSON(http.StatusOK, observation)
}

func (a *API) listAssets(c *gin.Context) {
	closes := a.store.ListCloses()
	if len(closes) == 0 {
		c.JSON(http.StatusOK, gin.H{"items": []Asset{}})
		return
	}
	closeRun := closes[0]
	assets := make([]Asset, 0, len(closeRun.Steps))
	for _, definition := range MonthlyCloudClose {
		step := closeRun.Steps[definition.Key]
		if step == nil {
			continue
		}
		status := "waiting"
		if step.Verification == VerificationPassed {
			status = "fresh"
		} else if step.Verification == VerificationFailed {
			status = "failed"
		}
		assets = append(assets, Asset{
			Key:                    definition.Key,
			Label:                  definition.Title,
			BusinessPeriod:         closeRun.BillMonth,
			LastExecutionSucceeded: step.LastSucceededAt,
			LastOutputVerified:     step.LastVerifiedAt,
			Status:                 status,
			Owner:                  definition.Owner,
			PhysicalScope:          definition.Scope,
		})
	}
	c.JSON(http.StatusOK, gin.H{"items": assets})
}

func (a *API) authorizeMutation() gin.HandlerFunc {
	return func(c *gin.Context) {
		if a.runtimeMode != "demo" {
			writeAPIError(c, http.StatusForbidden, "execution_contract_unbound", "v7 执行门禁尚未通过；真实业务写入未开放", nil)
			c.Abort()
			return
		}
		if !a.writeActionsEnabled {
			writeAPIError(c, http.StatusForbidden, "write_actions_disabled", "write actions are disabled for this runtime", nil)
			c.Abort()
			return
		}
		if a.mutationToken == "" {
			c.Next()
			return
		}
		if c.GetHeader("Authorization") != "Bearer "+a.mutationToken {
			writeAPIError(c, http.StatusUnauthorized, "mutation_token_invalid", "mutation token is invalid", nil)
			c.Abort()
			return
		}
		c.Next()
	}
}

func utcNow() time.Time { return time.Now().UTC() }

func previousBillMonth() string {
	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		location = time.FixedZone("Asia/Shanghai", 8*60*60)
	}
	return time.Now().In(location).AddDate(0, -1, 0).Format("2006-01")
}

func writeAPIError(c *gin.Context, status int, code, message string, details any) {
	requestID := c.GetString("request_id")
	payload := gin.H{"code": code, "message": message, "request_id": requestID}
	if details != nil {
		payload["details"] = details
	}
	c.JSON(status, gin.H{"error": payload})
}
