package com.cameleer.server.app.controller;

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.rbac.GroupDetail;
import com.cameleer.server.core.rbac.GroupRepository;
import com.cameleer.server.core.rbac.GroupSummary;
import com.cameleer.server.core.rbac.RbacService;
import com.cameleer.server.core.rbac.SystemRole;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
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.security.access.prepost.PreAuthorize;
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.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * Admin endpoints for group management.
 * Protected by {@code ROLE_ADMIN}.
 */
@RestController
@RequestMapping("/api/v1/admin/groups")
@Tag(name = "Group Admin", description = "Group management (ADMIN only)")
@PreAuthorize("hasRole('ADMIN')")
public class GroupAdminController {

    private final GroupRepository groupRepository;
    private final AuditService auditService;
    private final RbacService rbacService;

    public GroupAdminController(GroupRepository groupRepository, AuditService auditService,
                                RbacService rbacService) {
        this.groupRepository = groupRepository;
        this.auditService = auditService;
        this.rbacService = rbacService;
    }

    @GetMapping
    @Operation(summary = "List all groups with hierarchy and effective roles")
    @ApiResponse(responseCode = "200", description = "Group list returned")
    public ResponseEntity<List<GroupDetail>> listGroups() {
        List<GroupSummary> summaries = groupRepository.findAll();
        List<GroupDetail> details = new ArrayList<>();
        for (GroupSummary summary : summaries) {
            groupRepository.findById(summary.id()).ifPresent(details::add);
        }
        return ResponseEntity.ok(details);
    }

    @GetMapping("/{id}")
    @Operation(summary = "Get group by ID with effective roles")
    @ApiResponse(responseCode = "200", description = "Group found")
    @ApiResponse(responseCode = "404", description = "Group not found")
    public ResponseEntity<GroupDetail> getGroup(@PathVariable UUID id) {
        return groupRepository.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    @Operation(summary = "Create a new group")
    @ApiResponse(responseCode = "200", description = "Group created")
    public ResponseEntity<Map<String, UUID>> createGroup(@RequestBody CreateGroupRequest request,
                                                          HttpServletRequest httpRequest) {
        UUID id = groupRepository.create(request.name(), request.parentGroupId());
        auditService.log("create_group", AuditCategory.RBAC, id.toString(),
                Map.of("name", request.name()), AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.ok(Map.of("id", id));
    }

    @PutMapping("/{id}")
    @Operation(summary = "Update group name or parent")
    @ApiResponse(responseCode = "200", description = "Group updated")
    @ApiResponse(responseCode = "404", description = "Group not found")
    @ApiResponse(responseCode = "409", description = "Cycle detected in group hierarchy")
    public ResponseEntity<Void> updateGroup(@PathVariable UUID id,
                                             @RequestBody UpdateGroupRequest request,
                                             HttpServletRequest httpRequest) {
        Optional<GroupDetail> existing = groupRepository.findById(id);
        if (existing.isEmpty()) {
            return ResponseEntity.notFound().build();
        }

        // Cycle detection: walk ancestor chain of proposed parent and check if it includes 'id'
        if (request.parentGroupId() != null) {
            List<GroupSummary> ancestors = groupRepository.findAncestorChain(request.parentGroupId());
            for (GroupSummary ancestor : ancestors) {
                if (ancestor.id().equals(id)) {
                    return ResponseEntity.status(409).build();
                }
            }
            // Also check that the proposed parent itself is not the group being updated
            if (request.parentGroupId().equals(id)) {
                return ResponseEntity.status(409).build();
            }
        }

        groupRepository.update(id, request.name(), request.parentGroupId());
        auditService.log("update_group", AuditCategory.RBAC, id.toString(),
                null, AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "Delete group")
    @ApiResponse(responseCode = "204", description = "Group deleted")
    @ApiResponse(responseCode = "404", description = "Group not found")
    public ResponseEntity<Void> deleteGroup(@PathVariable UUID id,
                                             HttpServletRequest httpRequest) {
        if (groupRepository.findById(id).isEmpty()) {
            return ResponseEntity.notFound().build();
        }
        groupRepository.delete(id);
        auditService.log("delete_group", AuditCategory.RBAC, id.toString(),
                null, AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.noContent().build();
    }

    @PostMapping("/{id}/roles/{roleId}")
    @Operation(summary = "Assign a role to a group")
    @ApiResponse(responseCode = "200", description = "Role assigned to group")
    @ApiResponse(responseCode = "404", description = "Group not found")
    public ResponseEntity<Void> assignRoleToGroup(@PathVariable UUID id,
                                                   @PathVariable UUID roleId,
                                                   HttpServletRequest httpRequest) {
        if (groupRepository.findById(id).isEmpty()) {
            return ResponseEntity.notFound().build();
        }
        groupRepository.addRole(id, roleId);
        auditService.log("assign_role_to_group", AuditCategory.RBAC, id.toString(),
                Map.of("roleId", roleId), AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/{id}/roles/{roleId}")
    @Operation(summary = "Remove a role from a group")
    @ApiResponse(responseCode = "204", description = "Role removed from group")
    @ApiResponse(responseCode = "404", description = "Group not found")
    public ResponseEntity<Void> removeRoleFromGroup(@PathVariable UUID id,
                                                     @PathVariable UUID roleId,
                                                     HttpServletRequest httpRequest) {
        if (groupRepository.findById(id).isEmpty()) {
            return ResponseEntity.notFound().build();
        }
        if (SystemRole.ADMIN_ID.equals(roleId) && rbacService.getEffectivePrincipalsForRole(SystemRole.ADMIN_ID).size() <= 1) {
            throw new ResponseStatusException(HttpStatus.CONFLICT,
                    "Cannot remove the ADMIN role: at least one admin user must exist");
        }
        groupRepository.removeRole(id, roleId);
        auditService.log("remove_role_from_group", AuditCategory.RBAC, id.toString(),
                Map.of("roleId", roleId), AuditResult.SUCCESS, httpRequest);
        return ResponseEntity.noContent().build();
    }

    public record CreateGroupRequest(String name, UUID parentGroupId) {}
    public record UpdateGroupRequest(String name, UUID parentGroupId) {}
}
