package handler

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"security-center-backend/internal/modules/department/service"
	"security-center-backend/internal/pkg/lark"
	"security-center-backend/internal/pkg/response"
	settingssvc "security-center-backend/internal/service/settings"

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

const pickerReadsBlockedMessage = "org sync snapshot is expired; Feishu-backed picker reads are blocked until a fresh sync succeeds"

type orgSyncRuntimeConfig struct {
	CronExpr      string
	LarkAppID     string
	LarkAppSecret string
}

type orgSyncConfigError struct {
	status  int
	message string
}

func (e orgSyncConfigError) Error() string {
	return e.message
}

type Handler struct {
	deptSvc     *service.DepartmentService
	runtime     *service.OrgSyncRuntimeControl
	settingsSvc *settingssvc.Service
}

func New(deptSvc *service.DepartmentService, runtime *service.OrgSyncRuntimeControl, settingsSvc *settingssvc.Service) *Handler {
	return &Handler{
		deptSvc:     deptSvc,
		runtime:     runtime,
		settingsSvc: settingsSvc,
	}
}

func (h *Handler) RegisterRoutes(rg *gin.RouterGroup) {
	deps := rg.Group("/departments")
	deps.GET("", h.ListDepartments)
	deps.GET("/:id/employees", h.ListEmployees)
	deps.POST("/sync", h.Sync)
	deps.GET("/sync/runs", h.ListSyncRuns)
	deps.GET("/employees/search", h.SearchEmployees)

	// Runtime control endpoints
	deps.GET("/runtime/status", h.GetRuntimeStatus)
	deps.POST("/runtime/start", h.StartRuntime)
	deps.POST("/runtime/stop", h.StopRuntime)
	deps.POST("/runtime/reconfigure", h.ReconfigureRuntime)
}

func (h *Handler) ListDepartments(c *gin.Context) {
	if h.pickerReadsBlocked() {
		response.Error(c, http.StatusForbidden, pickerReadsBlockedMessage)
		return
	}
	depts, err := h.deptSvc.GetDepartments(c.Request.Context())
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, depts)
}

func (h *Handler) ListEmployees(c *gin.Context) {
	if h.pickerReadsBlocked() {
		response.Error(c, http.StatusForbidden, pickerReadsBlockedMessage)
		return
	}
	deptID := c.Param("id")
	emps, err := h.deptSvc.GetEmployeesByDepartment(c.Request.Context(), deptID)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, emps)
}

func (h *Handler) Sync(c *gin.Context) {
	cfg, err := h.loadOrgSyncManualConfig(c.Request.Context())
	if err != nil {
		h.writeOrgSyncConfigError(c, err)
		return
	}
	client := lark.NewClient(cfg.LarkAppID, cfg.LarkAppSecret)
	if err := client.GetTenantAccessToken(c.Request.Context()); err != nil {
		response.Error(c, http.StatusBadGateway, fmt.Sprintf("Feishu credential validation failed; check Org Sync App ID/App Secret, app tenant, API domain, and permissions: %v", err))
		return
	}
	h.deptSvc.SetLarkClient(client)

	run, err := h.deptSvc.StartSyncFromLarkAsync()
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, run)
}

func (h *Handler) SearchEmployees(c *gin.Context) {
	if h.pickerReadsBlocked() {
		response.Error(c, http.StatusForbidden, pickerReadsBlockedMessage)
		return
	}
	query := c.Query("q")
	if query == "" {
		response.Success(c, []interface{}{})
		return
	}
	emps, err := h.deptSvc.SearchEmployees(c.Request.Context(), query)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, emps)
}

func (h *Handler) ListSyncRuns(c *gin.Context) {
	runs, err := h.deptSvc.ListSyncRuns(c.Request.Context(), 20)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	response.Success(c, runs)
}

// GetRuntimeStatus returns the current status of the org sync runtime.
func (h *Handler) GetRuntimeStatus(c *gin.Context) {
	if h.runtime == nil {
		response.Error(c, http.StatusServiceUnavailable, "org sync runtime not configured")
		return
	}
	status := h.runtime.Status()
	response.Success(c, status)
}

// StartRuntime starts the org sync scheduler with the provided configuration.
func (h *Handler) StartRuntime(c *gin.Context) {
	if h.runtime == nil {
		response.Error(c, http.StatusServiceUnavailable, "org sync runtime not configured")
		return
	}

	cfg, err := h.loadOrgSyncRuntimeConfig(c.Request.Context())
	if err != nil {
		h.writeOrgSyncConfigError(c, err)
		return
	}

	status, err := h.runtime.Start(cfg.CronExpr, cfg.LarkAppID, cfg.LarkAppSecret)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}

	response.Success(c, status)
}

// StopRuntime stops the org sync scheduler.
func (h *Handler) StopRuntime(c *gin.Context) {
	if h.runtime == nil {
		response.Error(c, http.StatusServiceUnavailable, "org sync runtime not configured")
		return
	}
	status := h.runtime.Stop()
	response.Success(c, status)
}

// ReconfigureRuntime reconfigures the org sync scheduler with new settings.
func (h *Handler) ReconfigureRuntime(c *gin.Context) {
	if h.runtime == nil {
		response.Error(c, http.StatusServiceUnavailable, "org sync runtime not configured")
		return
	}

	cfg, err := h.loadOrgSyncRuntimeConfig(c.Request.Context())
	if err != nil {
		h.writeOrgSyncConfigError(c, err)
		return
	}

	status, err := h.runtime.Reconfigure(cfg.CronExpr, cfg.LarkAppID, cfg.LarkAppSecret)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}

	response.Success(c, status)
}

func (h *Handler) loadOrgSyncRuntimeConfig(ctx context.Context) (orgSyncRuntimeConfig, error) {
	cfg, orgSyncEnabled, err := h.loadOrgSyncConfig(ctx)
	if err != nil {
		return orgSyncRuntimeConfig{}, err
	}
	if !orgSyncEnabled {
		return orgSyncRuntimeConfig{}, orgSyncConfigError{
			status:  http.StatusBadRequest,
			message: "Org Sync is disabled; enable Org Sync before starting the scheduler",
		}
	}
	if cfg.CronExpr == "" {
		return orgSyncRuntimeConfig{}, orgSyncConfigError{
			status:  http.StatusBadRequest,
			message: "Org Sync cron expression is not configured in Data Sync settings",
		}
	}
	return cfg, nil
}

func (h *Handler) loadOrgSyncManualConfig(ctx context.Context) (orgSyncRuntimeConfig, error) {
	cfg, _, err := h.loadOrgSyncConfig(ctx)
	return cfg, err
}

func (h *Handler) loadOrgSyncConfig(ctx context.Context) (orgSyncRuntimeConfig, bool, error) {
	if h.settingsSvc == nil {
		return orgSyncRuntimeConfig{}, false, orgSyncConfigError{
			status:  http.StatusServiceUnavailable,
			message: "org sync settings service is not configured; cannot load Lark credentials",
		}
	}
	settings, err := h.settingsSvc.Get(ctx)
	if err != nil {
		return orgSyncRuntimeConfig{}, false, err
	}
	dataSync := settings.DataSync
	cfg := orgSyncRuntimeConfig{
		CronExpr:      strings.TrimSpace(dataSync.OrgSyncCron),
		LarkAppID:     strings.TrimSpace(dataSync.LarkAppID),
		LarkAppSecret: strings.TrimSpace(dataSync.LarkAppSecret),
	}
	if cfg.LarkAppID == "" || cfg.LarkAppSecret == "" {
		return orgSyncRuntimeConfig{}, dataSync.OrgSyncEnabled, orgSyncConfigError{
			status:  http.StatusBadRequest,
			message: "Org Sync Lark App ID and App Secret are not configured in Data Sync settings",
		}
	}
	return cfg, dataSync.OrgSyncEnabled, nil
}

func (h *Handler) writeOrgSyncConfigError(c *gin.Context, err error) {
	if cfgErr, ok := err.(orgSyncConfigError); ok {
		response.Error(c, cfgErr.status, cfgErr.message)
		return
	}
	response.Error(c, http.StatusInternalServerError, err.Error())
}

func (h *Handler) pickerReadsBlocked() bool {
	if h.runtime == nil {
		return false
	}
	status := h.runtime.Status()
	for _, scope := range status.BlockingScopes {
		if scope == "picker_reads" {
			return true
		}
	}
	return false
}
