package store

import (
	"context"
	"encoding/json"
	"strings"
	"time"
)

type AuditLog struct {
	ID                    string         `json:"id"`
	Category              string         `json:"category"`
	Action                string         `json:"action"`
	ActorGatewayUserID    string         `json:"actorGatewayUserId,omitempty"`
	ActorUserID           string         `json:"actorUserId,omitempty"`
	ActorUsername         string         `json:"actorUsername,omitempty"`
	ActorSource           string         `json:"actorSource,omitempty"`
	ActorRoles            []string       `json:"actorRoles,omitempty"`
	TargetType            string         `json:"targetType"`
	TargetID              string         `json:"targetId"`
	TargetGatewayUserID   string         `json:"targetGatewayUserId,omitempty"`
	TargetGatewayTenantID string         `json:"targetGatewayTenantId,omitempty"`
	RequestIP             string         `json:"requestIp,omitempty"`
	UserAgent             string         `json:"userAgent,omitempty"`
	BeforeState           map[string]any `json:"beforeState,omitempty"`
	AfterState            map[string]any `json:"afterState,omitempty"`
	Metadata              map[string]any `json:"metadata,omitempty"`
	CreatedAt             time.Time      `json:"createdAt"`
}

type AuditLogInput struct {
	Category              string         `json:"category"`
	Action                string         `json:"action"`
	ActorGatewayUserID    string         `json:"actorGatewayUserId"`
	ActorUserID           string         `json:"actorUserId"`
	ActorUsername         string         `json:"actorUsername"`
	ActorSource           string         `json:"actorSource"`
	ActorRoles            []string       `json:"actorRoles"`
	TargetType            string         `json:"targetType"`
	TargetID              string         `json:"targetId"`
	TargetGatewayUserID   string         `json:"targetGatewayUserId"`
	TargetGatewayTenantID string         `json:"targetGatewayTenantId"`
	RequestIP             string         `json:"requestIp"`
	UserAgent             string         `json:"userAgent"`
	BeforeState           map[string]any `json:"beforeState"`
	AfterState            map[string]any `json:"afterState"`
	Metadata              map[string]any `json:"metadata"`
}

type AuditLogFilter struct {
	Category   string
	Action     string
	TargetType string
	TargetID   string
	Limit      int
}

func (s *Store) RecordAuditLog(ctx context.Context, input AuditLogInput) (AuditLog, error) {
	return s.RecordAuditLogTx(ctx, s.pool, input)
}

func (s *Store) RecordAuditLogTx(ctx context.Context, tx Tx, input AuditLogInput) (AuditLog, error) {
	input = normalizeAuditLogInput(input)
	actorRolesJSON, _ := json.Marshal(input.ActorRoles)
	beforeJSON, _ := json.Marshal(emptyObjectIfNil(input.BeforeState))
	afterJSON, _ := json.Marshal(emptyObjectIfNil(input.AfterState))
	metadataJSON, _ := json.Marshal(emptyObjectIfNil(input.Metadata))
	return scanAuditLog(tx.QueryRow(ctx, `
INSERT INTO gateway_audit_logs (
  category, action, actor_gateway_user_id, actor_user_id, actor_username, actor_source,
  actor_roles, target_type, target_id, target_gateway_user_id, target_gateway_tenant_id,
  request_ip, user_agent, before_state, after_state, metadata
)
VALUES (
  $1, $2, NULLIF($3, '')::uuid, NULLIF($4, ''), NULLIF($5, ''), NULLIF($6, ''),
  $7::jsonb, $8, $9, NULLIF($10, '')::uuid, NULLIF($11, '')::uuid,
  NULLIF($12, ''), NULLIF($13, ''), $14::jsonb, $15::jsonb, $16::jsonb
)
RETURNING id::text, category, action, COALESCE(actor_gateway_user_id::text, ''), COALESCE(actor_user_id, ''),
          COALESCE(actor_username, ''), COALESCE(actor_source, ''), actor_roles,
          target_type, target_id, COALESCE(target_gateway_user_id::text, ''),
          COALESCE(target_gateway_tenant_id::text, ''), COALESCE(request_ip, ''),
          COALESCE(user_agent, ''), before_state, after_state, metadata, created_at`,
		input.Category,
		input.Action,
		input.ActorGatewayUserID,
		input.ActorUserID,
		input.ActorUsername,
		input.ActorSource,
		string(actorRolesJSON),
		input.TargetType,
		input.TargetID,
		input.TargetGatewayUserID,
		input.TargetGatewayTenantID,
		input.RequestIP,
		input.UserAgent,
		string(beforeJSON),
		string(afterJSON),
		string(metadataJSON),
	))
}

func (s *Store) ListAuditLogs(ctx context.Context, filter AuditLogFilter) ([]AuditLog, error) {
	limit := filter.Limit
	if limit <= 0 {
		limit = 100
	}
	if limit > 500 {
		limit = 500
	}
	rows, err := s.pool.Query(ctx, `
SELECT id::text, category, action, COALESCE(actor_gateway_user_id::text, ''), COALESCE(actor_user_id, ''),
       COALESCE(actor_username, ''), COALESCE(actor_source, ''), actor_roles,
       target_type, target_id, COALESCE(target_gateway_user_id::text, ''),
       COALESCE(target_gateway_tenant_id::text, ''), COALESCE(request_ip, ''),
       COALESCE(user_agent, ''), before_state, after_state, metadata, created_at
FROM gateway_audit_logs
WHERE (NULLIF($1, '') IS NULL OR category = $1)
  AND (NULLIF($2, '') IS NULL OR action = $2)
  AND (NULLIF($3, '') IS NULL OR target_type = $3)
  AND (NULLIF($4, '') IS NULL OR target_id = $4)
ORDER BY created_at DESC
LIMIT $5`,
		strings.TrimSpace(filter.Category),
		strings.TrimSpace(filter.Action),
		strings.TrimSpace(filter.TargetType),
		strings.TrimSpace(filter.TargetID),
		limit,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := make([]AuditLog, 0)
	for rows.Next() {
		item, err := scanAuditLog(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func normalizeAuditLogInput(input AuditLogInput) AuditLogInput {
	input.Category = firstNonEmpty(strings.TrimSpace(input.Category), "system")
	input.Action = strings.TrimSpace(input.Action)
	input.TargetType = strings.TrimSpace(input.TargetType)
	input.TargetID = strings.TrimSpace(input.TargetID)
	input.ActorSource = firstNonEmpty(strings.TrimSpace(input.ActorSource), "gateway")
	return input
}

func scanAuditLog(row scanner) (AuditLog, error) {
	var item AuditLog
	var actorRoles []byte
	var beforeState []byte
	var afterState []byte
	var metadata []byte
	if err := row.Scan(
		&item.ID,
		&item.Category,
		&item.Action,
		&item.ActorGatewayUserID,
		&item.ActorUserID,
		&item.ActorUsername,
		&item.ActorSource,
		&actorRoles,
		&item.TargetType,
		&item.TargetID,
		&item.TargetGatewayUserID,
		&item.TargetGatewayTenantID,
		&item.RequestIP,
		&item.UserAgent,
		&beforeState,
		&afterState,
		&metadata,
		&item.CreatedAt,
	); err != nil {
		return AuditLog{}, err
	}
	item.ActorRoles = decodeStringArray(actorRoles)
	item.BeforeState = decodeObject(beforeState)
	item.AfterState = decodeObject(afterState)
	item.Metadata = decodeObject(metadata)
	return item, nil
}
