package de.effigenix.infrastructure.usermanagement.web.controller;

import de.effigenix.application.usermanagement.*;
import de.effigenix.application.usermanagement.command.*;
import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.domain.usermanagement.UserError;
import de.effigenix.infrastructure.usermanagement.web.dto.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * REST Controller for User Management endpoints.
 *
 * Security:
 * - All endpoints require authentication (JWT token)
 * - @PreAuthorize as defense-in-depth (authorization also enforced in use cases)
 *
 * Infrastructure Layer → REST API
 */
@RestController
@RequestMapping("/api/users")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "User Management", description = "User management endpoints (requires authentication)")
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    private final CreateUser createUser;
    private final GetUser getUser;
    private final ListUsers listUsers;
    private final UpdateUser updateUser;
    private final LockUser lockUser;
    private final UnlockUser unlockUser;
    private final AssignRole assignRole;
    private final RemoveRole removeRole;
    private final ChangePassword changePassword;

    public UserController(
            CreateUser createUser,
            GetUser getUser,
            ListUsers listUsers,
            UpdateUser updateUser,
            LockUser lockUser,
            UnlockUser unlockUser,
            AssignRole assignRole,
            RemoveRole removeRole,
            ChangePassword changePassword
    ) {
        this.createUser = createUser;
        this.getUser = getUser;
        this.listUsers = listUsers;
        this.updateUser = updateUser;
        this.lockUser = lockUser;
        this.unlockUser = unlockUser;
        this.assignRole = assignRole;
        this.removeRole = removeRole;
        this.changePassword = changePassword;
    }

    @PostMapping
    @PreAuthorize("hasAuthority('USER_WRITE')")
    @Operation(summary = "Create user (ADMIN only)", description = "Create a new user account with specified roles.")
    @ApiResponses({
            @ApiResponse(responseCode = "201", description = "User created successfully",
                    content = @Content(schema = @Schema(implementation = UserDTO.class))),
            @ApiResponse(responseCode = "400", description = "Validation error or invalid password"),
            @ApiResponse(responseCode = "403", description = "Missing permission"),
            @ApiResponse(responseCode = "409", description = "Username or email already exists")
    })
    public ResponseEntity<UserDTO> createUser(
            @Valid @RequestBody CreateUserRequest request,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Creating user: {} by actor: {}", request.username(), actorId.value());

        CreateUserCommand command = new CreateUserCommand(
                request.username(), request.email(), request.password(),
                request.roleNames(), request.branchId()
        );

        Result<UserError, UserDTO> result = createUser.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("User created successfully: {}", request.username());
        return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
    }

    @GetMapping
    @Operation(summary = "List all users", description = "Get a list of all users in the system.")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Users retrieved successfully"),
            @ApiResponse(responseCode = "401", description = "Authentication required")
    })
    public ResponseEntity<List<UserDTO>> listUsers(Authentication authentication) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Listing users by actor: {}", actorId.value());

        Result<UserError, List<UserDTO>> result = listUsers.execute(actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        return ResponseEntity.ok(result.unsafeGetValue());
    }

    @GetMapping("/{id}")
    @Operation(summary = "Get user by ID", description = "Retrieve a single user by their ID.")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "User retrieved successfully",
                    content = @Content(schema = @Schema(implementation = UserDTO.class))),
            @ApiResponse(responseCode = "404", description = "User not found"),
            @ApiResponse(responseCode = "401", description = "Authentication required")
    })
    public ResponseEntity<UserDTO> getUserById(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Getting user: {} by actor: {}", userId, actorId.value());

        Result<UserError, UserDTO> result = getUser.execute(userId, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        return ResponseEntity.ok(result.unsafeGetValue());
    }

    @PutMapping("/{id}")
    @Operation(summary = "Update user", description = "Update user details (email, branchId).")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "User updated successfully",
                    content = @Content(schema = @Schema(implementation = UserDTO.class))),
            @ApiResponse(responseCode = "404", description = "User not found"),
            @ApiResponse(responseCode = "409", description = "Email already exists")
    })
    public ResponseEntity<UserDTO> updateUser(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            @Valid @RequestBody UpdateUserRequest request,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Updating user: {} by actor: {}", userId, actorId.value());

        UpdateUserCommand command = new UpdateUserCommand(userId, request.email(), request.branchId());
        Result<UserError, UserDTO> result = updateUser.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("User updated successfully: {}", userId);
        return ResponseEntity.ok(result.unsafeGetValue());
    }

    @PostMapping("/{id}/lock")
    @PreAuthorize("hasAuthority('USER_LOCK')")
    @Operation(summary = "Lock user (ADMIN only)", description = "Lock a user account (prevents login).")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "User locked successfully",
                    content = @Content(schema = @Schema(implementation = UserDTO.class))),
            @ApiResponse(responseCode = "404", description = "User not found"),
            @ApiResponse(responseCode = "409", description = "Invalid status transition"),
            @ApiResponse(responseCode = "403", description = "Missing permission")
    })
    public ResponseEntity<UserDTO> lockUser(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Locking user: {} by actor: {}", userId, actorId.value());

        LockUserCommand command = new LockUserCommand(userId);
        Result<UserError, UserDTO> result = lockUser.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("User locked successfully: {}", userId);
        return ResponseEntity.ok(result.unsafeGetValue());
    }

    @PostMapping("/{id}/unlock")
    @PreAuthorize("hasAuthority('USER_UNLOCK')")
    @Operation(summary = "Unlock user (ADMIN only)", description = "Unlock a user account (allows login).")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "User unlocked successfully",
                    content = @Content(schema = @Schema(implementation = UserDTO.class))),
            @ApiResponse(responseCode = "404", description = "User not found"),
            @ApiResponse(responseCode = "409", description = "Invalid status transition"),
            @ApiResponse(responseCode = "403", description = "Missing permission")
    })
    public ResponseEntity<UserDTO> unlockUser(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Unlocking user: {} by actor: {}", userId, actorId.value());

        UnlockUserCommand command = new UnlockUserCommand(userId);
        Result<UserError, UserDTO> result = unlockUser.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("User unlocked successfully: {}", userId);
        return ResponseEntity.ok(result.unsafeGetValue());
    }

    @PostMapping("/{id}/roles")
    @PreAuthorize("hasAuthority('ROLE_ASSIGN')")
    @Operation(summary = "Assign role (ADMIN only)", description = "Assign a role to a user.")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Role assigned successfully",
                    content = @Content(schema = @Schema(implementation = UserDTO.class))),
            @ApiResponse(responseCode = "404", description = "User or role not found"),
            @ApiResponse(responseCode = "403", description = "Missing permission")
    })
    public ResponseEntity<UserDTO> assignRole(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            @Valid @RequestBody AssignRoleRequest request,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Assigning role {} to user: {} by actor: {}", request.roleName(), userId, actorId.value());

        AssignRoleCommand command = new AssignRoleCommand(userId, request.roleName());
        Result<UserError, UserDTO> result = assignRole.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("Role assigned successfully to user: {}", userId);
        return ResponseEntity.ok(result.unsafeGetValue());
    }

    @DeleteMapping("/{id}/roles/{roleName}")
    @PreAuthorize("hasAuthority('ROLE_REMOVE')")
    @Operation(summary = "Remove role (ADMIN only)", description = "Remove a role from a user.")
    @ApiResponses({
            @ApiResponse(responseCode = "204", description = "Role removed successfully"),
            @ApiResponse(responseCode = "404", description = "User or role not found"),
            @ApiResponse(responseCode = "403", description = "Missing permission")
    })
    public ResponseEntity<Void> removeRole(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            @Parameter(description = "Role name") @PathVariable("roleName") RoleName roleName,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Removing role {} from user: {} by actor: {}", roleName, userId, actorId.value());

        RemoveRoleCommand command = new RemoveRoleCommand(userId, roleName);
        Result<UserError, UserDTO> result = removeRole.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("Role removed successfully from user: {}", userId);
        return ResponseEntity.noContent().build();
    }

    @PutMapping("/{id}/password")
    @Operation(summary = "Change password", description = "Change user password. Requires current password for verification.")
    @ApiResponses({
            @ApiResponse(responseCode = "204", description = "Password changed successfully"),
            @ApiResponse(responseCode = "400", description = "Invalid password"),
            @ApiResponse(responseCode = "401", description = "Invalid current password"),
            @ApiResponse(responseCode = "404", description = "User not found")
    })
    public ResponseEntity<Void> changePassword(
            @Parameter(description = "User ID") @PathVariable("id") String userId,
            @Valid @RequestBody ChangePasswordRequest request,
            Authentication authentication
    ) {
        ActorId actorId = extractActorId(authentication);
        logger.info("Changing password for user: {} by actor: {}", userId, actorId.value());

        ChangePasswordCommand command = new ChangePasswordCommand(userId, request.currentPassword(), request.newPassword());
        Result<UserError, Void> result = changePassword.execute(command, actorId);
        if (result.isFailure()) throw new DomainErrorException(result.unsafeGetError());

        logger.info("Password changed successfully for user: {}", userId);
        return ResponseEntity.noContent().build();
    }

    private ActorId extractActorId(Authentication authentication) {
        if (authentication == null || authentication.getName() == null) {
            throw new IllegalStateException("No authentication found in SecurityContext");
        }
        return ActorId.of(authentication.getName());
    }

    public static class DomainErrorException extends RuntimeException {
        private final UserError error;

        public DomainErrorException(UserError error) {
            super(error.message());
            this.error = error;
        }

        public UserError getError() {
            return error;
        }
    }
}
