package com.davos.assessment.auth;

import com.davos.assessment.common.ApiException;
import com.davos.assessment.audit.AuditService;
import com.davos.assessment.invitation.InvitationCode;
import com.davos.assessment.invitation.InvitationCodeRepository;
import com.davos.assessment.legal.LegalService;
import com.davos.assessment.legal.ConsentType;
import com.davos.assessment.profile.Profile;
import com.davos.assessment.profile.ProfileRepository;
import com.davos.assessment.project.Project;
import com.davos.assessment.project.ProjectAccessService;
import com.davos.assessment.project.ProjectMembership;
import com.davos.assessment.project.ProjectMembershipRepository;
import com.davos.assessment.project.ProjectMembershipRole;
import com.davos.assessment.user.AppUser;
import com.davos.assessment.user.UserRepository;
import com.davos.assessment.user.UserRole;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.Optional;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class AuthController {
  private final AuthenticationManager authenticationManager;
  private final JwtService jwtService;
  private final UserRepository userRepository;
  private final InvitationCodeRepository invitationCodeRepository;
  private final ProfileRepository profileRepository;
  private final PasswordEncoder passwordEncoder;
  private final AuditService auditService;
  private final LegalService legalService;
  private final ProjectAccessService projectAccessService;
  private final ProjectMembershipRepository membershipRepository;

  public AuthController(
      AuthenticationManager authenticationManager,
      JwtService jwtService,
      UserRepository userRepository,
      InvitationCodeRepository invitationCodeRepository,
      ProfileRepository profileRepository,
      PasswordEncoder passwordEncoder,
      AuditService auditService,
      LegalService legalService,
      ProjectAccessService projectAccessService,
      ProjectMembershipRepository membershipRepository) {
    this.authenticationManager = authenticationManager;
    this.jwtService = jwtService;
    this.userRepository = userRepository;
    this.invitationCodeRepository = invitationCodeRepository;
    this.profileRepository = profileRepository;
    this.passwordEncoder = passwordEncoder;
    this.auditService = auditService;
    this.legalService = legalService;
    this.projectAccessService = projectAccessService;
    this.membershipRepository = membershipRepository;
  }

  @PostMapping("/auth/register")
  @Transactional
  public AuthResponse register(@Valid @RequestBody RegisterRequest request, HttpServletRequest httpServletRequest) {
    return registerForProject(ProjectAccessService.DEFAULT_PROJECT_SLUG, request, httpServletRequest);
  }

  @PostMapping("/projects/{slug}/auth/register")
  @Transactional
  public AuthResponse registerForProject(
      @PathVariable String slug,
      @Valid @RequestBody RegisterRequest request,
      HttpServletRequest httpServletRequest) {
    Project project = projectAccessService.findEnabledProject(slug);
    if (!request.password().equals(request.confirmPassword())) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "两次输入的密码不一致");
    }
    String email = request.email().trim().toLowerCase();
    if (userRepository.existsByEmailIgnoreCase(email)) {
      throw new ApiException(HttpStatus.CONFLICT, "EMAIL_EXISTS_LOGIN_TO_JOIN");
    }

    InvitationCode code = invitationCodeRepository.findLockedByProjectAndCodeIgnoreCase(project, request.invitationCode())
        .orElseThrow(() -> new ApiException(HttpStatus.BAD_REQUEST, "邀请码无效"));
    if (!code.canUse()) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "邀请码已失效或使用次数已满");
    }
    if (!request.agreeTerms()) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "请先勾选同意用户协议与隐私条款");
    }
    LegalService.LegalBundle bundle = legalService.currentBundle(project);
    if (!bundle.userAgreement().version().equals(request.userAgreementVersion())
        || !bundle.privacyPolicy().version().equals(request.privacyPolicyVersion())) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "协议版本已更新，请重新确认后注册");
    }

    AppUser user = new AppUser();
    user.setEmail(email);
    user.setPasswordHash(passwordEncoder.encode(request.password()));
    user.setRole(UserRole.USER);
    try {
      userRepository.saveAndFlush(user);
    } catch (DataIntegrityViolationException ex) {
      throw new ApiException(HttpStatus.CONFLICT, "该邮箱已经注册");
    }

    code.setUsedCount(code.getUsedCount() + 1);
    invitationCodeRepository.save(code);
    ProjectMembership membership = new ProjectMembership();
    membership.setProject(project);
    membership.setUser(user);
    membership.setRole(ProjectMembershipRole.PARTICIPANT);
    membership.setEnabled(true);
    membershipRepository.save(membership);

    legalService.record(project, user, ConsentType.USER_AGREEMENT, request.userAgreementVersion(), true, httpServletRequest);
    legalService.record(project, user, ConsentType.PRIVACY_POLICY, request.privacyPolicyVersion(), true, httpServletRequest);
    auditService.record(project, user, "REGISTER", "USER", user.getId().toString());

    return AuthResponse.project(user, jwtService.createToken(user), project, ProjectMembershipRole.PARTICIPANT, false);
  }

  @PostMapping("/auth/login")
  public AuthResponse login(@Valid @RequestBody LoginRequest request) {
    authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.email(), request.password()));
    AppUser user = userRepository.findByEmailIgnoreCase(request.email())
        .orElseThrow(() -> new ApiException(HttpStatus.UNAUTHORIZED, "账号或密码不正确"));
    auditService.record(user, "LOGIN", "USER", user.getId().toString());
    String target = request.target() == null ? "" : request.target().trim().toUpperCase();
    if ("SUPER".equals(target)) {
      if (!user.getRole().isPlatformSuper()) {
        throw new ApiException(HttpStatus.FORBIDDEN, "需要平台超级管理员权限");
      }
      return AuthResponse.superAdmin(user, jwtService.createToken(user));
    }
    String slug = request.projectSlug() == null || request.projectSlug().isBlank()
        ? ProjectAccessService.DEFAULT_PROJECT_SLUG
        : request.projectSlug().trim().toLowerCase();
    Project project = projectAccessService.findEnabledProject(slug);
    ProjectMembershipRole role = projectAccessService.membership(project, user)
        .map(ProjectMembership::getRole)
        .orElseGet(() -> {
          if (user.getRole().isPlatformSuper()) return ProjectMembershipRole.PROJECT_ADMIN;
          throw new ApiException(HttpStatus.FORBIDDEN, "当前账号不属于该项目");
        });
    boolean hasProfile = profileRepository.findByProjectAndUser(project, user).isPresent();
    return AuthResponse.project(user, jwtService.createToken(user), project, role, hasProfile);
  }

  @GetMapping("/me")
  public AuthResponse me(@AuthenticationPrincipal UserPrincipal principal, @RequestParam(required = false) String projectSlug) {
    if (projectSlug == null || projectSlug.isBlank()) {
      Optional<Profile> profile = profileRepository.findByUser(principal.user());
      return AuthResponse.basic(principal.user(), null, profile.isPresent());
    }
    Project project = projectAccessService.findEnabledProject(projectSlug);
    ProjectMembershipRole role = projectAccessService.membership(project, principal.user())
        .map(ProjectMembership::getRole)
        .orElseGet(() -> principal.user().getRole().isPlatformSuper() ? ProjectMembershipRole.PROJECT_ADMIN : null);
    boolean hasProfile = profileRepository.findByProjectAndUser(project, principal.user()).isPresent();
    return role == null
        ? AuthResponse.basic(principal.user(), null, hasProfile)
        : AuthResponse.project(principal.user(), null, project, role, hasProfile);
  }

  @PostMapping("/projects/{slug}/auth/join")
  @Transactional
  public AuthResponse joinProject(
      @AuthenticationPrincipal UserPrincipal principal,
      @PathVariable String slug,
      @Valid @RequestBody JoinRequest request,
      HttpServletRequest httpServletRequest) {
    Project project = projectAccessService.findEnabledProject(slug);
    AppUser user = principal.user();
    if (membershipRepository.findByProjectAndUser(project, user).isPresent()) {
      boolean hasProfile = profileRepository.findByProjectAndUser(project, user).isPresent();
      ProjectMembership membership = membershipRepository.findByProjectAndUser(project, user).orElseThrow();
      return AuthResponse.project(user, null, project, membership.getRole(), hasProfile);
    }
    if (!request.agreeTerms()) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "请先勾选同意用户协议与隐私条款");
    }
    LegalService.LegalBundle bundle = legalService.currentBundle(project);
    if (!bundle.userAgreement().version().equals(request.userAgreementVersion())
        || !bundle.privacyPolicy().version().equals(request.privacyPolicyVersion())) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "协议版本已更新，请重新确认后加入项目");
    }
    InvitationCode code = invitationCodeRepository.findLockedByProjectAndCodeIgnoreCase(project, request.invitationCode())
        .orElseThrow(() -> new ApiException(HttpStatus.BAD_REQUEST, "邀请码无效"));
    if (!code.canUse()) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "邀请码已失效或使用次数已满");
    }
    code.setUsedCount(code.getUsedCount() + 1);
    invitationCodeRepository.save(code);

    ProjectMembership membership = new ProjectMembership();
    membership.setProject(project);
    membership.setUser(user);
    membership.setRole(ProjectMembershipRole.PARTICIPANT);
    membership.setEnabled(true);
    membershipRepository.save(membership);
    legalService.record(project, user, ConsentType.USER_AGREEMENT, request.userAgreementVersion(), true, httpServletRequest);
    legalService.record(project, user, ConsentType.PRIVACY_POLICY, request.privacyPolicyVersion(), true, httpServletRequest);
    auditService.record(project, user, "JOIN_PROJECT", "PROJECT", project.getSlug());
    return AuthResponse.project(user, null, project, ProjectMembershipRole.PARTICIPANT, false);
  }

  public record RegisterRequest(
      @Email(message = "请输入正确的邮箱") String email,
      @Size(min = 8, message = "密码至少 8 位") String password,
      @Size(min = 8, message = "确认密码至少 8 位") String confirmPassword,
      @NotBlank(message = "请输入邀请码") String invitationCode,
      boolean agreeTerms,
      @NotBlank(message = "用户协议版本不能为空") String userAgreementVersion,
      @NotBlank(message = "隐私条款版本不能为空") String privacyPolicyVersion) {}

  public record JoinRequest(
      @NotBlank(message = "请输入邀请码") String invitationCode,
      boolean agreeTerms,
      @NotBlank(message = "用户协议版本不能为空") String userAgreementVersion,
      @NotBlank(message = "隐私条款版本不能为空") String privacyPolicyVersion) {}

  public record LoginRequest(
      @Email(message = "请输入正确的邮箱") String email,
      @NotBlank String password,
      String projectSlug,
      String target) {}

  public record ProjectAuth(Long id, String slug, String role, boolean hasProfile) {}

  public record AuthResponse(
      Long id,
      String email,
      String role,
      String globalRole,
      String token,
      boolean hasProfile,
      ProjectAuth project,
      String defaultRoute) {
    static AuthResponse basic(AppUser user, String token, boolean hasProfile) {
      return new AuthResponse(user.getId(), user.getEmail(), user.getRole().name(), user.getRole().name(), token, hasProfile, null, hasProfile ? "/catalog" : "/profile");
    }

    static AuthResponse superAdmin(AppUser user, String token) {
      return new AuthResponse(user.getId(), user.getEmail(), user.getRole().name(), user.getRole().name(), token, false, null, "/super");
    }

    static AuthResponse project(AppUser user, String token, Project project, ProjectMembershipRole role, boolean hasProfile) {
      String defaultRoute = role == ProjectMembershipRole.PROJECT_ADMIN
          ? "/" + project.getSlug() + "/admin"
          : (hasProfile ? "/" + project.getSlug() + "/catalog" : "/" + project.getSlug() + "/profile");
      return new AuthResponse(
          user.getId(),
          user.getEmail(),
          role == ProjectMembershipRole.PROJECT_ADMIN ? "PROJECT_ADMIN" : "USER",
          user.getRole().name(),
          token,
          hasProfile,
          new ProjectAuth(project.getId(), project.getSlug(), role.name(), hasProfile),
          defaultRoute);
    }
  }
}
