package com.cameleer.server.app.controller;

import com.cameleer.common.model.LogEntry;
import com.cameleer.server.app.metrics.ServerMetrics;
import com.cameleer.server.app.security.JwtAuthenticationFilter;
import com.cameleer.server.core.ingestion.BufferedLogEntry;
import java.util.List;
import com.cameleer.server.core.ingestion.WriteBuffer;
import com.cameleer.server.core.agent.AgentInfo;
import com.cameleer.server.core.agent.AgentRegistryService;
import com.cameleer.server.core.security.JwtService.JwtValidationResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import com.cameleer.server.app.config.TenantProperties;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/data")
@Tag(name = "Ingestion", description = "Data ingestion endpoints")
public class LogIngestionController {

    private static final Logger log = LoggerFactory.getLogger(LogIngestionController.class);

    private final WriteBuffer<BufferedLogEntry> logBuffer;
    private final AgentRegistryService registryService;
    private final TenantProperties tenantProperties;
    private final ServerMetrics serverMetrics;

    public LogIngestionController(WriteBuffer<BufferedLogEntry> logBuffer,
                                  AgentRegistryService registryService,
                                  TenantProperties tenantProperties,
                                  ServerMetrics serverMetrics) {
        this.logBuffer = logBuffer;
        this.registryService = registryService;
        this.tenantProperties = tenantProperties;
        this.serverMetrics = serverMetrics;
    }

    @PostMapping("/logs")
    @Operation(summary = "Ingest application log entries",
            description = "Accepts a batch of log entries from an agent. Entries are buffered and flushed periodically.")
    @ApiResponse(responseCode = "202", description = "Logs accepted for indexing")
    public ResponseEntity<Void> ingestLogs(@RequestBody List<LogEntry> entries,
                                              HttpServletRequest request) {
        String instanceId = extractAgentId();
        if (instanceId == null || instanceId.isBlank()) {
            log.warn("Log ingestion rejected: no agent identity in request (unauthenticated or missing principal)");
            serverMetrics.recordIngestionDrop("no_identity");
            return ResponseEntity.accepted().build();
        }

        if (entries == null || entries.isEmpty()) {
            log.warn("Log ingestion from instance={}: empty or null payload", instanceId);
            return ResponseEntity.accepted().build();
        }

        String applicationId;
        String environment;

        AgentInfo agent = registryService.findById(instanceId);
        if (agent != null) {
            applicationId = agent.applicationId();
            environment = agent.environmentId() != null ? agent.environmentId() : "default";
        } else {
            // Agent not yet in registry (e.g. server just restarted) — fall back to JWT claims
            JwtValidationResult jwt = (JwtValidationResult) request.getAttribute(JwtAuthenticationFilter.JWT_RESULT_ATTR);
            applicationId = jwt != null ? jwt.application() : null;
            environment = jwt != null && jwt.environment() != null ? jwt.environment() : "default";
            if (applicationId != null) {
                log.debug("Log ingestion from instance={}: agent not in registry, using JWT claims (app={}, env={})",
                        instanceId, applicationId, environment);
            }
        }

        if (applicationId == null || applicationId.isBlank()) {
            log.warn("Log ingestion from instance={}: no applicationId from registry or JWT. {} entries dropped.",
                    instanceId, entries.size());
            serverMetrics.recordIngestionDrops("no_agent", entries.size());
            return ResponseEntity.accepted().build();
        }

        log.debug("Ingesting {} log entries from instance={}, app={}, env={}", entries.size(), instanceId, applicationId, environment);

        int accepted = 0;
        int dropped = 0;
        for (var entry : entries) {
            boolean offered = logBuffer.offer(new BufferedLogEntry(
                    tenantProperties.getId(), environment, instanceId, applicationId, entry));
            if (offered) {
                accepted++;
            } else {
                dropped++;
            }
        }

        if (dropped > 0) {
            log.warn("Log buffer full: accepted={}, dropped={} from instance={}, app={}",
                    accepted, dropped, instanceId, applicationId);
            serverMetrics.recordIngestionDrops("buffer_full", dropped);
        } else {
            log.debug("Accepted {} log entries from instance={}, app={}", accepted, instanceId, applicationId);
        }

        return ResponseEntity.accepted().build();
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<Void> handleDeserializationError(HttpMessageNotReadableException ex) {
        String instanceId = extractAgentId();
        log.warn("Log ingestion from instance={}: failed to deserialize request body: {}",
                instanceId != null ? instanceId : "unknown", ex.getMostSpecificCause().getMessage());
        return ResponseEntity.badRequest().build();
    }

    private String extractAgentId() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return auth != null ? auth.getName() : null;
    }
}
