package com.cameleer.server.app.controller;

import com.cameleer.server.app.dto.ActiveQueryResponse;
import com.cameleer.server.app.dto.ConnectionPoolResponse;
import com.cameleer.server.app.dto.DatabaseStatusResponse;
import com.cameleer.server.app.dto.TableSizeResponse;
import com.cameleer.server.core.admin.AuditCategory;
import com.cameleer.server.core.admin.AuditResult;
import com.cameleer.server.core.admin.AuditService;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
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.RequestMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import javax.sql.DataSource;
import java.util.List;

@ConditionalOnProperty(
    name = "cameleer.server.security.infrastructureendpoints",
    havingValue = "true",
    matchIfMissing = true
)
@RestController
@RequestMapping("/api/v1/admin/database")
@PreAuthorize("hasRole('ADMIN')")
@Tag(name = "Database Admin", description = "Database monitoring and management (ADMIN only)")
public class DatabaseAdminController {

    private final JdbcTemplate jdbc;
    private final DataSource dataSource;
    private final AuditService auditService;

    public DatabaseAdminController(JdbcTemplate jdbc, DataSource dataSource,
                                   AuditService auditService) {
        this.jdbc = jdbc;
        this.dataSource = dataSource;
        this.auditService = auditService;
    }

    @GetMapping("/status")
    @Operation(summary = "Get database connection status and version")
    public ResponseEntity<DatabaseStatusResponse> getStatus() {
        try {
            String version = jdbc.queryForObject("SELECT version()", String.class);
            String schema = jdbc.queryForObject("SELECT current_schema()", String.class);
            String host = extractHost(dataSource);
            return ResponseEntity.ok(new DatabaseStatusResponse(true, version, host, schema));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                    .body(new DatabaseStatusResponse(false, null, null, null));
        }
    }

    @GetMapping("/pool")
    @Operation(summary = "Get HikariCP connection pool stats")
    public ResponseEntity<ConnectionPoolResponse> getPool() {
        HikariDataSource hds = (HikariDataSource) dataSource;
        HikariPoolMXBean pool = hds.getHikariPoolMXBean();
        return ResponseEntity.ok(new ConnectionPoolResponse(
                pool.getActiveConnections(), pool.getIdleConnections(),
                pool.getThreadsAwaitingConnection(), hds.getConnectionTimeout(),
                hds.getMaximumPoolSize()));
    }

    @GetMapping("/tables")
    @Operation(summary = "Get table sizes and row counts")
    public ResponseEntity<List<TableSizeResponse>> getTables() {
        var tables = jdbc.query("""
                SELECT relname AS table_name,
                       n_live_tup AS row_count,
                       pg_size_pretty(pg_total_relation_size(relid)) AS data_size,
                       pg_total_relation_size(relid) AS data_size_bytes,
                       pg_size_pretty(pg_indexes_size(relid)) AS index_size,
                       pg_indexes_size(relid) AS index_size_bytes
                FROM pg_stat_user_tables
                WHERE schemaname = current_schema()
                ORDER BY pg_total_relation_size(relid) DESC
                """, (rs, row) -> new TableSizeResponse(
                rs.getString("table_name"), rs.getLong("row_count"),
                rs.getString("data_size"), rs.getString("index_size"),
                rs.getLong("data_size_bytes"), rs.getLong("index_size_bytes")));
        return ResponseEntity.ok(tables);
    }

    @GetMapping("/queries")
    @Operation(summary = "Get active queries")
    public ResponseEntity<List<ActiveQueryResponse>> getQueries() {
        var queries = jdbc.query("""
                SELECT pid, EXTRACT(EPOCH FROM (now() - query_start)) AS duration_seconds,
                       state, query
                FROM pg_stat_activity
                WHERE state != 'idle' AND pid != pg_backend_pid() AND datname = current_database()
                  AND application_name = current_setting('application_name')
                ORDER BY query_start ASC
                """, (rs, row) -> new ActiveQueryResponse(
                rs.getInt("pid"), rs.getDouble("duration_seconds"),
                rs.getString("state"), rs.getString("query")));
        return ResponseEntity.ok(queries);
    }

    @PostMapping("/queries/{pid}/kill")
    @Operation(summary = "Terminate a query by PID")
    public ResponseEntity<Void> killQuery(@PathVariable int pid, HttpServletRequest request) {
        var exists = jdbc.queryForObject(
                "SELECT EXISTS(SELECT 1 FROM pg_stat_activity WHERE pid = ? AND pid != pg_backend_pid() AND application_name = current_setting('application_name'))",
                Boolean.class, pid);
        if (!Boolean.TRUE.equals(exists)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No active query with PID " + pid);
        }
        jdbc.queryForObject("SELECT pg_terminate_backend(?)", Boolean.class, pid);
        auditService.log("kill_query", AuditCategory.INFRA, "PID " + pid, null, AuditResult.SUCCESS, request);
        return ResponseEntity.ok().build();
    }

    private String extractHost(DataSource ds) {
        try {
            if (ds instanceof HikariDataSource hds) {
                return hds.getJdbcUrl();
            }
            return "unknown";
        } catch (Exception e) {
            return "unknown";
        }
    }
}
