package com.datamate.common.auth.application;

import com.datamate.common.auth.domain.model.AuthPermissionInfo;
import com.datamate.common.auth.domain.model.AuthRoleInfo;
import com.datamate.common.auth.domain.model.AuthUserAccount;
import com.datamate.common.auth.domain.model.AuthUserSummary;
import com.datamate.common.auth.infrastructure.exception.AuthErrorCode;
import com.datamate.common.auth.infrastructure.persistence.mapper.AuthMapper;
import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
import com.datamate.common.auth.interfaces.rest.dto.AuthUserView;
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
import com.datamate.common.infrastructure.exception.BusinessAssert;
import com.datamate.common.security.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 认证授权应用服务
 */
@Service
@RequiredArgsConstructor
public class AuthApplicationService {
    private static final String TOKEN_TYPE = "Bearer";

    private final AuthMapper authMapper;
    private final JwtUtils jwtUtils;
    private final PasswordEncoder passwordEncoder;

    public AuthLoginResponse login(String username, String password) {
        AuthUserAccount user = authMapper.findUserByUsername(username);
        BusinessAssert.notNull(user, AuthErrorCode.INVALID_CREDENTIALS);
        BusinessAssert.isTrue(Boolean.TRUE.equals(user.getEnabled()), AuthErrorCode.ACCOUNT_DISABLED);
        BusinessAssert.isTrue(passwordEncoder.matches(password, user.getPasswordHash()), AuthErrorCode.INVALID_CREDENTIALS);

        AuthBundle authBundle = loadAuthBundle(user.getId());
        String token = buildToken(authBundle);
        authMapper.updateLastLoginAt(user.getId());

        return new AuthLoginResponse(
                token,
                TOKEN_TYPE,
                computeExpiresInSeconds(token),
                toUserView(authBundle.user()),
                authBundle.roleCodes(),
                authBundle.permissionCodes()
        );
    }

    public AuthCurrentUserResponse getCurrentUser(String token) {
        Claims claims = parseClaims(token);
        Long userId = parseUserId(claims);
        AuthBundle authBundle = loadAuthBundle(userId);
        return new AuthCurrentUserResponse(
                toUserView(authBundle.user()),
                authBundle.roleCodes(),
                authBundle.permissionCodes()
        );
    }

    public AuthLoginResponse refreshToken(String token) {
        Claims claims = parseClaims(token);
        Long userId = parseUserId(claims);
        AuthBundle authBundle = loadAuthBundle(userId);
        String refreshedToken = buildToken(authBundle);
        return new AuthLoginResponse(
                refreshedToken,
                TOKEN_TYPE,
                computeExpiresInSeconds(refreshedToken),
                toUserView(authBundle.user()),
                authBundle.roleCodes(),
                authBundle.permissionCodes()
        );
    }

    public List<AuthUserWithRolesResponse> listUsersWithRoles() {
        List<AuthUserSummary> users = authMapper.listUsers();
        List<AuthUserWithRolesResponse> responses = new ArrayList<>(users.size());
        for (AuthUserSummary user : users) {
            List<String> roleCodes = authMapper.findRolesByUserId(user.getId())
                    .stream()
                    .map(AuthRoleInfo::getRoleCode)
                    .filter(Objects::nonNull)
                    .toList();
            responses.add(new AuthUserWithRolesResponse(
                    user.getId(),
                    user.getUsername(),
                    user.getFullName(),
                    user.getEmail(),
                    user.getEnabled(),
                    roleCodes
            ));
        }
        return responses;
    }

    public List<AuthRoleInfo> listRoles() {
        return authMapper.listRoles();
    }

    public List<AuthPermissionInfo> listPermissions() {
        return authMapper.listPermissions();
    }

    public void assignUserRoles(Long userId, List<String> roleIds) {
        AuthUserAccount user = authMapper.findUserById(userId);
        BusinessAssert.notNull(user, AuthErrorCode.USER_NOT_FOUND);

        Set<String> distinctRoleIds = new LinkedHashSet<>(roleIds);
        BusinessAssert.notEmpty(distinctRoleIds, AuthErrorCode.ROLE_NOT_FOUND);

        int existingRoleCount = authMapper.countRolesByIds(new ArrayList<>(distinctRoleIds));
        BusinessAssert.isTrue(existingRoleCount == distinctRoleIds.size(), AuthErrorCode.ROLE_NOT_FOUND);

        authMapper.deleteUserRoles(userId);
        authMapper.insertUserRoles(userId, new ArrayList<>(distinctRoleIds));
    }

    private String buildToken(AuthBundle authBundle) {
        Map<String, Object> claims = Map.of(
                "userId", authBundle.user().getId(),
                "roles", authBundle.roleCodes(),
                "permissions", authBundle.permissionCodes()
        );
        return jwtUtils.generateToken(authBundle.user().getUsername(), claims);
    }

    private AuthBundle loadAuthBundle(Long userId) {
        AuthUserAccount user = authMapper.findUserById(userId);
        BusinessAssert.notNull(user, AuthErrorCode.USER_NOT_FOUND);
        BusinessAssert.isTrue(Boolean.TRUE.equals(user.getEnabled()), AuthErrorCode.ACCOUNT_DISABLED);

        List<String> roleCodes = authMapper.findRolesByUserId(userId).stream()
                .map(AuthRoleInfo::getRoleCode)
                .filter(Objects::nonNull)
                .toList();
        List<String> permissionCodes = authMapper.findPermissionCodesByUserId(userId).stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        return new AuthBundle(user, roleCodes, permissionCodes);
    }

    private Claims parseClaims(String token) {
        try {
            return jwtUtils.getClaimsFromToken(token);
        } catch (JwtException | IllegalArgumentException e) {
            throw com.datamate.common.infrastructure.exception.BusinessException.of(AuthErrorCode.TOKEN_INVALID);
        }
    }

    private Long parseUserId(Claims claims) {
        Object userIdObject = claims.get("userId");
        if (userIdObject instanceof Number number) {
            return number.longValue();
        }
        if (userIdObject instanceof String str) {
            try {
                return Long.parseLong(str);
            } catch (NumberFormatException e) {
                throw com.datamate.common.infrastructure.exception.BusinessException.of(AuthErrorCode.TOKEN_INVALID);
            }
        }
        throw com.datamate.common.infrastructure.exception.BusinessException.of(AuthErrorCode.TOKEN_INVALID);
    }

    private long computeExpiresInSeconds(String token) {
        Date expirationDate = jwtUtils.getExpirationDateFromToken(token);
        long seconds = Duration.between(new Date().toInstant(), expirationDate.toInstant()).toSeconds();
        return Math.max(seconds, 0L);
    }

    private AuthUserView toUserView(AuthUserAccount user) {
        return new AuthUserView(
                user.getId(),
                user.getUsername(),
                user.getFullName(),
                user.getEmail(),
                user.getAvatarUrl(),
                user.getOrganization()
        );
    }

    private record AuthBundle(
            AuthUserAccount user,
            List<String> roleCodes,
            List<String> permissionCodes
    ) {
    }
}
