package com.gameplatform.server.service.auth;

import com.gameplatform.server.mapper.account.UserAccountMapper;
import com.gameplatform.server.model.dto.auth.LoginRequest;
import com.gameplatform.server.model.dto.auth.LoginResponse;
import com.gameplatform.server.model.entity.account.UserAccount;
import com.gameplatform.server.security.JwtService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.Map;

@Service
public class AuthService {
    private static final Logger log = LoggerFactory.getLogger(AuthService.class);
    private final UserAccountMapper userAccountMapper;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;

    public AuthService(UserAccountMapper userAccountMapper,
                       PasswordEncoder passwordEncoder,
                       JwtService jwtService) {
        this.userAccountMapper = userAccountMapper;
        this.passwordEncoder = passwordEncoder;
        this.jwtService = jwtService;
    }

    public Mono<LoginResponse> login(LoginRequest req) {
        log.info("login attempt username={}", req.getUsername());
        long start = System.currentTimeMillis();
        return Mono.fromCallable(() -> userAccountMapper.findByUsername(req.getUsername()))
                .subscribeOn(Schedulers.boundedElastic())
                .doOnNext(acc -> {
                    if (acc == null) {
                        log.warn("login account not found username={}", req.getUsername());
                    } else {
                        log.debug("login account loaded id={}, status={}, userType={}", acc.getId(), acc.getStatus(), acc.getUserType());
                    }
                })
                .flatMap(acc -> validatePasswordAndBuild(acc, req.getPassword()))
                .doOnSuccess(r -> log.info("login success username={}, tookMs={}", req.getUsername(), (System.currentTimeMillis()-start)))
                .doOnError(e -> log.warn("login failed username={}, err={}, tookMs={}", req.getUsername(), e.toString(), (System.currentTimeMillis()-start)));
    }

    private Mono<LoginResponse> validatePasswordAndBuild(UserAccount acc, String rawPwd) {
        if (acc == null || acc.getPasswordHash() == null) {
            log.debug("validatePasswordAndBuild: account missing or no password hash");
            return Mono.error(new IllegalArgumentException("用户名或密码错误"));
        }
        boolean ok;
        String hash = acc.getPasswordHash();
        if (hash.startsWith("$2a$") || hash.startsWith("$2b$") || hash.startsWith("$2y$")) {
            ok = passwordEncoder.matches(rawPwd, hash);
        } else if (hash.startsWith("PLAIN:")) {
            ok = rawPwd.equals(hash.substring(6));
        } else {
            ok = false;
        }
        if (!ok) {
            log.debug("validatePasswordAndBuild: password not match for user id={}", acc.getId());
            return Mono.error(new IllegalArgumentException("用户名或密码错误"));
        }
        if (!"ENABLED".equalsIgnoreCase(acc.getStatus())) {
            log.debug("validatePasswordAndBuild: account disabled id={}", acc.getId());
            return Mono.error(new IllegalStateException("账户已禁用"));
        }

        String userType = acc.getUserType() == null ? "agent" : acc.getUserType().toLowerCase();
        String token = jwtService.generateToken(
                userType + ":" + acc.getId(),
                userType, acc.getId(), acc.getUsername(),
                Map.of()
        );
        LoginResponse resp = new LoginResponse();
        resp.setAccessToken(token);
        resp.setUserType(userType);
        resp.setUserId(acc.getId());
        resp.setUsername(acc.getUsername());
        resp.setExpiresIn(60L * 30);
        return Mono.just(resp);
    }
}
