package api

import (
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

	"wucher/internal/service"
	sharedconst "wucher/internal/shared/const"
)

func AuditMiddleware(auditSvc *service.AuditLogService) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if auditSvc == nil {
			return c.Next()
		}
		start := time.Now()
		err := c.Next()
		status := c.Response().StatusCode()
		if status == 0 {
			status = fiber.StatusOK
		}

		requestID := c.GetRespHeader(fiber.HeaderXRequestID)
		if requestID == "" {
			requestID = localString(c.Locals("requestid"))
		}

		meta := map[string]any{
			"latency_ms": time.Since(start).Milliseconds(),
		}
		if err != nil {
			meta["error"] = err.Error()
		}

		module, action := resolveAuditModuleAction(c)
		if !shouldPersistAuditByMethod(c.Method()) {
			return err
		}

		auditSvc.Log(c.UserContext(), service.AuditLogInput{
			RequestID:   requestID,
			ActorUserID: localUserID(c.Locals("user_id")),
			Layer:       "api",
			Module:      module,
			Action:      action,
			Method:      c.Method(),
			Path:        c.Path(),
			StatusCode:  status,
			Success:     status >= 200 && status < 400 && err == nil,
			IP:          c.IP(),
			UserAgent:   c.Get(fiber.HeaderUserAgent),
			Message:     "HTTP " + strconv.Itoa(status),
			Metadata:    meta,
		})

		return err
	}
}

func shouldPersistAuditByMethod(method string) bool {
	switch strings.ToUpper(strings.TrimSpace(method)) {
	case fiber.MethodPost, fiber.MethodPut, fiber.MethodPatch, fiber.MethodDelete:
		return true
	default:
		return false
	}
}

func resolveAuditModuleAction(c *fiber.Ctx) (string, string) {
	method := c.Method()
	path := c.Path()
	normalized := strings.ToLower(strings.TrimSpace(path))
	parts := strings.Split(strings.Trim(normalized, "/"), "/")
	if len(parts) < 3 || parts[0] != "api" || parts[1] != "v1" {
		return sharedconst.AuditModuleUnknown, actionFromMethod(method)
	}
	moduleSegment := strings.TrimSpace(parts[2])
	module := moduleFromPathSegment(moduleSegment)
	if moduleSegment == "bases" {
		module = resolveBaseModuleVariant(c, module)
	}
	suffix := ""
	if len(parts) > 3 {
		suffix = strings.TrimSpace(parts[len(parts)-1])
	}
	switch suffix {
	case "get-all", "dt":
		return module, sharedconst.AuditActionList
	case "get":
		return module, sharedconst.AuditActionGet
	case "create":
		return module, sharedconst.AuditActionCreate
	case "update":
		return module, sharedconst.AuditActionUpdate
	case "delete":
		return module, sharedconst.AuditActionDelete
	case "export":
		return module, sharedconst.AuditActionExport
	}
	return module, actionFromMethod(method)
}

func resolveBaseModuleVariant(c *fiber.Ctx, fallback string) string {
	raw := strings.TrimSpace(c.Query("category_type"))
	if raw == "" {
		raw = strings.TrimSpace(c.Query("base_category"))
	}
	if raw == "" {
		raw = strings.TrimSpace(c.Query("category"))
	}
	switch strings.ToLower(raw) {
	case "regular":
		return sharedconst.AuditModuleBaseRegular
	case "hems":
		return sharedconst.AuditModuleBaseHEMS
	default:
		return fallback
	}
}

func actionFromMethod(method string) string {
	switch strings.ToUpper(strings.TrimSpace(method)) {
	case fiber.MethodGet:
		return sharedconst.AuditActionGet
	case fiber.MethodPost:
		return sharedconst.AuditActionCreate
	case fiber.MethodPut, fiber.MethodPatch:
		return sharedconst.AuditActionUpdate
	case fiber.MethodDelete:
		return sharedconst.AuditActionDelete
	default:
		return sharedconst.AuditActionCustom
	}
}

func moduleFromPathSegment(segment string) string {
	switch strings.ToLower(strings.TrimSpace(segment)) {
	case "air-rescuer-checklist":
		return sharedconst.AuditModuleAirRescuerChecklist
	case "audit-logs":
		return sharedconst.AuditModuleAuditLog
	case "auth":
		return sharedconst.AuditModuleAuth
	case "bases":
		return sharedconst.AuditModuleBase
	case "branding", "public":
		return sharedconst.AuditModuleBranding
	case "contacts":
		return sharedconst.AuditModuleContact
	case "duty-roster":
		return sharedconst.AuditModuleDutyRoster
	case "dul":
		return sharedconst.AuditModuleDUL
	case "facilities":
		return sharedconst.AuditModuleFacility
	case "federal-state":
		return sharedconst.AuditModuleFederalState
	case "file-manager":
		return sharedconst.AuditModuleFileManager
	case "flight-data":
		return sharedconst.AuditModuleFlightData
	case "flights":
		return sharedconst.AuditModuleFlight
	case "forces-present":
		return sharedconst.AuditModuleForcesPresent
	case "health-insurance-companies":
		return sharedconst.AuditModuleHealthInsuranceCompany
	case "helicopter-files":
		return sharedconst.AuditModuleHelicopterFile
	case "helicopters":
		return sharedconst.AuditModuleHelicopter
	case "hems-operation", "hems-operation-category", "hems-operational-data":
		return sharedconst.AuditModuleUnknown
	case "hospital":
		return sharedconst.AuditModuleHospital
	case "icao":
		return sharedconst.AuditModuleICAO
	case "insurance-patient-data":
		return sharedconst.AuditModuleInsurancePatientData
	case "land":
		return sharedconst.AuditModuleLand
	case "master-settings":
		return sharedconst.AuditModuleMasterSetting
	case "medicine":
		return sharedconst.AuditModuleMedicine
	case "mission":
		return sharedconst.AuditModuleMission
	case "opc":
		return sharedconst.AuditModuleOPC
	case "patient-data":
		return sharedconst.AuditModulePatientData
	case "reserve-acs":
		return sharedconst.AuditModuleReserveAC
	case "roles":
		return sharedconst.AuditModuleRole
	case "users":
		return sharedconst.AuditModuleUser
	case "vocation":
		return sharedconst.AuditModuleVocation
	default:
		return sharedconst.AuditModuleUnknown
	}
}

func localString(v any) string {
	s, _ := v.(string)
	return s
}

func localUserID(v any) []byte {
	id, _ := v.([]byte)
	if len(id) != 16 {
		return nil
	}
	return id
}
