package handlers

import (
	"encoding/json"
	"fmt"
	"slices"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

	"wucher/internal/domain/audit"
	"wucher/internal/service"
	sharedconst "wucher/internal/shared/const"
	"wucher/internal/shared/pkg/uuidv7"
	"wucher/internal/transport/http/dto"
	"wucher/internal/transport/http/jsonapi"
	"wucher/internal/transport/http/response"
)

type AuditHandler struct {
	svc *service.AuditLogService
}

func NewAuditHandler(svc *service.AuditLogService) *AuditHandler {
	return &AuditHandler{svc: svc}
}

// ListAuditLogs godoc
// @Summary List audit logs
// @Description JSON:API list with pagination, filtering and sorting.
// @Tags Audit Logs
// @Produce json
// @Param filter[search] query string false "Search by request_id/layer/action/method/path/message (contains)"
// @Param filter[method] query string false "Method filter; single/comma-separated values or ALL/*" Enums(POST,PUT,PATCH,DELETE,UPDATE,ALL,*)
// @Param method query string false "Alias for filter[method]" Enums(POST,PUT,PATCH,DELETE,UPDATE,ALL,*)
// @Param filter[module] query string false "Module filter; single/comma-separated values or ALL/*" Enums(air_rescuer_checklist,audit_log,auth,base,base_regular,base_hems,base_roster,branding,contact,duty_roster,facility,federal_state,file_manager,flight,flight_data,forces_present,health_insurance_company,helicopter,helicopter_file,hems_roster,hospital,icao,insurance_patient_data,land,master_setting,medicine,mission,opc,patient_data,reserve_ac,role,user,vocation,unknown,ALL,*)
// @Param module query string false "Alias for filter[module]" Enums(air_rescuer_checklist,audit_log,auth,base,base_regular,base_hems,base_roster,branding,contact,duty_roster,facility,federal_state,file_manager,flight,flight_data,forces_present,health_insurance_company,helicopter,helicopter_file,hems_roster,hospital,icao,insurance_patient_data,land,master_setting,medicine,mission,opc,patient_data,reserve_ac,role,user,vocation,unknown,ALL,*)
// @Param filter[action] query string false "Action filter; single/comma-separated values or ALL/*" Enums(list,get,create,update,delete,export,custom,ALL,*)
// @Param action query string false "Alias for filter[action]" Enums(list,get,create,update,delete,export,custom,ALL,*)
// @Param page[number] query int false "Page number (default 1)"
// @Param page[size] query int false "Page size (default 20, max 200)"
// @Param sort query string false "Sort order" Enums(created_at,-created_at,module,-module,action,-action,layer,-layer,status_code,-status_code,success,-success)
// @Success 200 {object} jsonapi.Document
// @Failure 400 {object} dto.ErrorResponse
// @Failure 422 {object} dto.ErrorResponse
// @Router /api/v1/audit-logs/get-all [get]
func (h *AuditHandler) List(c *fiber.Ctx) error {
	filter := c.Query("filter[search]")
	if filter == "" {
		filter = c.Query("filter[action]")
	}
	if filter == "" {
		filter = c.Query("filter[layer]")
	}
	methodFilter := c.Query("filter[method]")
	if methodFilter == "" {
		methodFilter = c.Query("method")
	}
	methods := parseAuditMethodFilter(methodFilter)
	moduleFilter := c.Query("filter[module]")
	if moduleFilter == "" {
		moduleFilter = c.Query("module")
	}
	modules := parseAuditTokenFilter(moduleFilter)
	actionFilter := c.Query("filter[action]")
	if actionFilter == "" {
		actionFilter = c.Query("action")
	}
	actions := parseAuditTokenFilter(actionFilter)

	pageNumber := parseIntDefault(c.Query("page[number]"), 1)
	pageSize := parseIntDefault(c.Query("page[size]"), 20)
	if pageSize > 200 {
		pageSize = 200
	}
	if pageNumber < 1 {
		pageNumber = 1
	}

	offset := (pageNumber - 1) * pageSize
	_ = offset
	sort := c.Query("sort")
	if errObj := validateAuditListQuery(methods, modules, actions, sort); errObj != nil {
		return response.WriteErrors(c, fiber.StatusUnprocessableEntity, []jsonapi.ErrorObject{*errObj})
	}

	logs, total, err := h.svc.List(c.UserContext(), filter, methods, modules, actions, sort, 0, 0)
	if err != nil {
		return response.WriteErrors(c, fiber.StatusBadRequest, []jsonapi.ErrorObject{{
			Status: "400",
			Title:  "List failed",
			Detail: safeInternalDetail(),
		}})
	}

	data := make([]dto.AuditLogResource, 0, len(logs))
	for i := range logs {
		data = append(data, auditLogResource(&logs[i]))
	}

	meta := map[string]any{
		"total": total,
	}
	return response.WriteWithMeta(c, fiber.StatusOK, data, meta)
}

func parseAuditMethodFilter(raw string) []string {
	v := strings.TrimSpace(raw)
	if v == "" {
		return nil
	}
	parts := strings.Split(v, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		token := strings.TrimSpace(part)
		if token == "" {
			continue
		}
		out = append(out, token)
	}
	return out
}

func auditLogResource(v *audit.AuditLog) dto.AuditLogResource {
	id, _ := uuidv7.BytesToString(v.ID)
	actorID := ""
	if len(v.ActorUserID) == 16 {
		actorID, _ = uuidv7.BytesToString(v.ActorUserID)
	}

	var metadata any
	if len(v.Metadata) > 0 {
		_ = json.Unmarshal(v.Metadata, &metadata)
	}
	module := strings.TrimSpace(v.Module)
	if module == "" {
		module = inferAuditModuleFromPath(strings.TrimSpace(v.Path))
	}

	return dto.AuditLogResource{
		Type: "audit_log",
		ID:   id,
		Attributes: dto.AuditLogAttributes{
			RequestID:   strings.TrimSpace(v.RequestID),
			ActorUserID: actorID,
			Layer:       strings.TrimSpace(v.Layer),
			Module:      module,
			Action:      strings.TrimSpace(v.Action),
			Method:      strings.TrimSpace(v.Method),
			Path:        strings.TrimSpace(v.Path),
			StatusCode:  v.StatusCode,
			Success:     v.Success,
			IP:          strings.TrimSpace(v.IP),
			UserAgent:   strings.TrimSpace(v.UserAgent),
			Message:     strings.TrimSpace(v.Message),
			Metadata:    metadata,
			CreatedAt:   v.CreatedAt.UTC().Format(time.RFC3339),
		},
	}
}

func inferAuditModuleFromPath(path string) string {
	parts := strings.Split(strings.Trim(strings.ToLower(strings.TrimSpace(path)), "/"), "/")
	if len(parts) < 3 || parts[0] != "api" || parts[1] != "v1" {
		return sharedconst.AuditModuleUnknown
	}
	switch parts[2] {
	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 parseAuditTokenFilter(raw string) []string {
	v := strings.TrimSpace(raw)
	if v == "" {
		return nil
	}
	parts := strings.Split(v, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		token := strings.TrimSpace(part)
		if token == "" {
			continue
		}
		out = append(out, token)
	}
	return out
}

func validateAuditListQuery(methods, modules, actions []string, sort string) *jsonapi.ErrorObject {
	for _, method := range methods {
		v := strings.ToUpper(strings.TrimSpace(method))
		if v == "" || v == "ALL" || v == "*" {
			continue
		}
		if !slices.Contains([]string{"POST", "PUT", "PATCH", "DELETE", "UPDATE"}, v) {
			return &jsonapi.ErrorObject{
				Status: "422",
				Title:  "Validation error",
				Detail: fmt.Sprintf("invalid method filter: %s", method),
				Source: &jsonapi.ErrorSource{Pointer: "/query/method"},
			}
		}
	}
	validModules := []string{
		sharedconst.AuditModuleAirRescuerChecklist,
		sharedconst.AuditModuleAuditLog,
		sharedconst.AuditModuleAuth,
		sharedconst.AuditModuleBase,
		sharedconst.AuditModuleBaseRegular,
		sharedconst.AuditModuleBaseHEMS,
		sharedconst.AuditModuleBranding,
		sharedconst.AuditModuleContact,
		sharedconst.AuditModuleDutyRoster,
		sharedconst.AuditModuleDUL,
		sharedconst.AuditModuleFacility,
		sharedconst.AuditModuleFederalState,
		sharedconst.AuditModuleFileManager,
		sharedconst.AuditModuleFlightData,
		sharedconst.AuditModuleFlight,
		sharedconst.AuditModuleForcesPresent,
		sharedconst.AuditModuleHealthInsuranceCompany,
		sharedconst.AuditModuleHelicopterFile,
		sharedconst.AuditModuleHelicopter,
		sharedconst.AuditModuleHospital,
		sharedconst.AuditModuleICAO,
		sharedconst.AuditModuleInsurancePatientData,
		sharedconst.AuditModuleLand,
		sharedconst.AuditModuleMasterSetting,
		sharedconst.AuditModuleMedicine,
		sharedconst.AuditModuleMission,
		sharedconst.AuditModuleOPC,
		sharedconst.AuditModulePatientData,
		sharedconst.AuditModuleReserveAC,
		sharedconst.AuditModuleRole,
		sharedconst.AuditModuleUser,
		sharedconst.AuditModuleVocation,
		sharedconst.AuditModuleUnknown,
	}
	for _, module := range modules {
		v := strings.ToLower(strings.TrimSpace(module))
		if v == "" || v == "all" || v == "*" {
			continue
		}
		if !slices.Contains(validModules, v) {
			return &jsonapi.ErrorObject{
				Status: "422",
				Title:  "Validation error",
				Detail: fmt.Sprintf("invalid module filter: %s", module),
				Source: &jsonapi.ErrorSource{Pointer: "/query/module"},
			}
		}
	}
	validActions := []string{
		sharedconst.AuditActionList,
		sharedconst.AuditActionGet,
		sharedconst.AuditActionCreate,
		sharedconst.AuditActionUpdate,
		sharedconst.AuditActionDelete,
		sharedconst.AuditActionExport,
		sharedconst.AuditActionCustom,
	}
	for _, action := range actions {
		v := strings.ToLower(strings.TrimSpace(action))
		if v == "" || v == "all" || v == "*" {
			continue
		}
		if !slices.Contains(validActions, v) {
			return &jsonapi.ErrorObject{
				Status: "422",
				Title:  "Validation error",
				Detail: fmt.Sprintf("invalid action filter: %s", action),
				Source: &jsonapi.ErrorSource{Pointer: "/query/action"},
			}
		}
	}
	if strings.TrimSpace(sort) != "" {
		allowed := map[string]struct{}{
			"created_at": {}, "-created_at": {},
			"module": {}, "-module": {},
			"action": {}, "-action": {},
			"layer": {}, "-layer": {},
			"status_code": {}, "-status_code": {},
			"success": {}, "-success": {},
		}
		if _, ok := allowed[strings.TrimSpace(sort)]; !ok {
			return &jsonapi.ErrorObject{
				Status: "422",
				Title:  "Validation error",
				Detail: fmt.Sprintf("invalid sort: %s", sort),
				Source: &jsonapi.ErrorSource{Pointer: "/query/sort"},
			}
		}
	}
	return nil
}
