package com.cameleer.server.app.outbound.controller;

import com.cameleer.server.app.outbound.crypto.SecretCipher;
import com.cameleer.server.app.outbound.dto.OutboundConnectionDto;
import com.cameleer.server.app.outbound.dto.OutboundConnectionRequest;
import com.cameleer.server.app.outbound.dto.OutboundConnectionTestResult;
import com.cameleer.server.core.admin.AuditCategory;
import com.cameleer.server.core.admin.AuditResult;
import com.cameleer.server.core.admin.AuditService;
import com.cameleer.server.core.http.OutboundHttpClientFactory;
import com.cameleer.server.core.http.OutboundHttpRequestContext;
import com.cameleer.server.core.outbound.OutboundConnection;
import com.cameleer.server.core.outbound.OutboundConnectionService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/v1/admin/outbound-connections")
@PreAuthorize("hasRole('ADMIN')")
@Tag(name = "Outbound Connections Admin", description = "Admin-managed outbound HTTPS destinations")
public class OutboundConnectionAdminController {

    private final OutboundConnectionService service;
    private final SecretCipher cipher;
    private final AuditService audit;
    private final OutboundHttpClientFactory httpClientFactory;

    public OutboundConnectionAdminController(OutboundConnectionService service, SecretCipher cipher,
                                             AuditService audit, OutboundHttpClientFactory httpClientFactory) {
        this.service = service;
        this.cipher = cipher;
        this.audit = audit;
        this.httpClientFactory = httpClientFactory;
    }

    @GetMapping
    @PreAuthorize("hasAnyRole('ADMIN','OPERATOR')")
    public List<OutboundConnectionDto> list() {
        return service.list().stream().map(OutboundConnectionDto::from).toList();
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAnyRole('ADMIN','OPERATOR')")
    public OutboundConnectionDto get(@PathVariable UUID id) {
        return OutboundConnectionDto.from(service.get(id));
    }

    @GetMapping("/{id}/usage")
    @PreAuthorize("hasAnyRole('ADMIN','OPERATOR')")
    public List<UUID> usage(@PathVariable UUID id) {
        return service.rulesReferencing(id);
    }

    @PostMapping
    public ResponseEntity<OutboundConnectionDto> create(@Valid @RequestBody OutboundConnectionRequest req,
                                                        HttpServletRequest httpRequest) {
        String userId = currentUserId();
        OutboundConnection draft = toDraft(req);
        OutboundConnection saved = service.create(draft, userId);
        audit.log("create_outbound_connection", AuditCategory.OUTBOUND_CONNECTION_CHANGE,
                saved.id().toString(), Map.of("name", saved.name()), AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.status(HttpStatus.CREATED).body(OutboundConnectionDto.from(saved));
    }

    @PutMapping("/{id}")
    public OutboundConnectionDto update(@PathVariable UUID id, @Valid @RequestBody OutboundConnectionRequest req,
                                        HttpServletRequest httpRequest) {
        String userId = currentUserId();
        OutboundConnection draft = toDraft(req);
        OutboundConnection saved = service.update(id, draft, userId);
        audit.log("update_outbound_connection", AuditCategory.OUTBOUND_CONNECTION_CHANGE,
                id.toString(), Map.of("name", saved.name()), AuditResult.SUCCESS, httpRequest);
        return OutboundConnectionDto.from(saved);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable UUID id, HttpServletRequest httpRequest) {
        String userId = currentUserId();
        service.delete(id, userId);
        audit.log("delete_outbound_connection", AuditCategory.OUTBOUND_CONNECTION_CHANGE,
                id.toString(), Map.of(), AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.noContent().build();
    }

    @PostMapping("/{id}/test")
    public OutboundConnectionTestResult test(@PathVariable UUID id, HttpServletRequest httpRequest) {
        String userId = currentUserId();
        OutboundConnection c = service.get(id);
        long t0 = System.currentTimeMillis();
        try {
            var ctx = new OutboundHttpRequestContext(c.tlsTrustMode(), c.tlsCaPemPaths(), null, null);
            CloseableHttpClient client = httpClientFactory.clientFor(ctx);
            HttpPost request = new HttpPost(c.url());
            request.setEntity(new StringEntity("{\"probe\":true}", ContentType.APPLICATION_JSON));
            try (var resp = client.execute(request)) {
                long latency = System.currentTimeMillis() - t0;
                HttpEntity entity = resp.getEntity();
                String body = entity == null ? "" : EntityUtils.toString(entity, StandardCharsets.UTF_8);
                String snippet = body.substring(0, Math.min(512, body.length()));
                audit.log("test_outbound_connection", AuditCategory.OUTBOUND_CONNECTION_CHANGE,
                        id.toString(), Map.of("status", resp.getCode(), "latencyMs", latency),
                        AuditResult.SUCCESS, httpRequest);
                return new OutboundConnectionTestResult(resp.getCode(), latency, snippet,
                        "TLS", null, null, null, null);
            }
        } catch (Exception e) {
            long latency = System.currentTimeMillis() - t0;
            audit.log("test_outbound_connection", AuditCategory.OUTBOUND_CONNECTION_CHANGE,
                    id.toString(), Map.of("error", e.getClass().getSimpleName(), "latencyMs", latency),
                    AuditResult.FAILURE, httpRequest);
            return new OutboundConnectionTestResult(0, latency, null, null, null, null, null, e.getMessage());
        }
    }

    private OutboundConnection toDraft(OutboundConnectionRequest req) {
        String cipherSecret = (req.hmacSecret() == null || req.hmacSecret().isBlank())
                ? null : cipher.encrypt(req.hmacSecret());
        // tenantId, id, timestamps, createdBy/updatedBy are filled by the service layer.
        return new OutboundConnection(
                null, null, req.name(), req.description(),
                req.url(), req.method(), req.defaultHeaders(), req.defaultBodyTmpl(),
                req.tlsTrustMode(), req.tlsCaPemPaths(),
                cipherSecret, req.auth(), req.allowedEnvironmentIds(),
                null, null, null, null);
    }

    private String currentUserId() {
        var auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || auth.getName() == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No authentication");
        }
        String name = auth.getName();
        return name.startsWith("user:") ? name.substring(5) : name;
    }
}
