package com.davos.assessment.project;

import com.davos.assessment.assessment.Assessment;
import com.davos.assessment.assessment.AssessmentRepository;
import com.davos.assessment.assessment.SubmissionRepository;
import com.davos.assessment.audit.AuditService;
import com.davos.assessment.auth.UserPrincipal;
import com.davos.assessment.common.ApiException;
import com.davos.assessment.invitation.InvitationCode;
import com.davos.assessment.invitation.InvitationCodeRepository;
import com.davos.assessment.report.ReportRepository;
import com.davos.assessment.user.AppUser;
import com.davos.assessment.user.UserRepository;
import com.davos.assessment.user.UserRole;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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/super")
public class SuperAdminController {
  private final ProjectRepository projectRepository;
  private final ProjectMembershipRepository membershipRepository;
  private final ProjectAssessmentRepository projectAssessmentRepository;
  private final AssessmentRepository assessmentRepository;
  private final UserRepository userRepository;
  private final InvitationCodeRepository invitationCodeRepository;
  private final SubmissionRepository submissionRepository;
  private final ReportRepository reportRepository;
  private final PasswordEncoder passwordEncoder;
  private final ProjectAccessService projectAccessService;
  private final ProjectDataExportService projectDataExportService;
  private final AuditService auditService;

  public SuperAdminController(
      ProjectRepository projectRepository,
      ProjectMembershipRepository membershipRepository,
      ProjectAssessmentRepository projectAssessmentRepository,
      AssessmentRepository assessmentRepository,
      UserRepository userRepository,
      InvitationCodeRepository invitationCodeRepository,
      SubmissionRepository submissionRepository,
      ReportRepository reportRepository,
      PasswordEncoder passwordEncoder,
      ProjectAccessService projectAccessService,
      ProjectDataExportService projectDataExportService,
      AuditService auditService) {
    this.projectRepository = projectRepository;
    this.membershipRepository = membershipRepository;
    this.projectAssessmentRepository = projectAssessmentRepository;
    this.assessmentRepository = assessmentRepository;
    this.userRepository = userRepository;
    this.invitationCodeRepository = invitationCodeRepository;
    this.submissionRepository = submissionRepository;
    this.reportRepository = reportRepository;
    this.passwordEncoder = passwordEncoder;
    this.projectAccessService = projectAccessService;
    this.projectDataExportService = projectDataExportService;
    this.auditService = auditService;
  }

  @GetMapping("/admins")
  @Transactional(readOnly = true)
  public List<PlatformAdminRow> platformAdmins(@AuthenticationPrincipal UserPrincipal principal) {
    projectAccessService.requirePlatformSuper(principal);
    return userRepository.findByRoleInOrderByCreatedAtDesc(List.of(UserRole.PLATFORM_SUPER_ADMIN, UserRole.SUPER_ADMIN)).stream()
        .map(PlatformAdminRow::from)
        .toList();
  }

  @PostMapping("/admins")
  @Transactional
  public PlatformAdminRow createPlatformAdmin(@AuthenticationPrincipal UserPrincipal principal, @Valid @RequestBody CreatePlatformAdminRequest request) {
    projectAccessService.requirePlatformSuper(principal);
    String email = normalizeEmail(request.email());
    if (userRepository.existsByEmailIgnoreCase(email)) {
      throw new ApiException(HttpStatus.CONFLICT, "平台管理员邮箱已存在");
    }
    AppUser user = new AppUser();
    user.setEmail(email);
    user.setPasswordHash(passwordEncoder.encode(request.password()));
    user.setRole(UserRole.PLATFORM_SUPER_ADMIN);
    userRepository.save(user);
    auditService.record(principal.user(), "CREATE_PLATFORM_SUPER_ADMIN", "USER", user.getId().toString());
    return PlatformAdminRow.from(user);
  }

  @GetMapping("/projects")
  @Transactional(readOnly = true)
  public List<ProjectRow> projects(@AuthenticationPrincipal UserPrincipal principal) {
    projectAccessService.requirePlatformSuper(principal);
    return projectRepository.findAllByOrderByCreatedAtDesc().stream().map(this::projectRow).toList();
  }

  @PostMapping("/projects")
  @Transactional
  public CreateProjectResponse createProject(@AuthenticationPrincipal UserPrincipal principal, @Valid @RequestBody CreateProjectRequest request) {
    projectAccessService.requirePlatformSuper(principal);
    String slug = normalizeSlug(request.slug());
    projectAccessService.validateSlug(slug);
    if (projectRepository.existsBySlug(slug)) {
      throw new ApiException(HttpStatus.CONFLICT, "项目 slug 已存在");
    }
    String adminEmail = normalizeEmail(request.adminEmail());
    if (userRepository.existsByEmailIgnoreCase(adminEmail)) {
      throw new ApiException(HttpStatus.CONFLICT, "项目管理员邮箱已存在");
    }

    Project project = new Project();
    project.setSlug(slug);
    project.setName(request.name().trim());
    project.setLandingTitle(request.landingTitle().trim());
    project.setLandingDescription(request.landingDescription().trim());
    project.setReportTitle(request.reportTitle().trim());
    project.setLogoUrl(request.logoUrl());
    project.setEnabled(true);
    project.setCreatedBy(principal.user());
    project.setUpdatedAt(Instant.now());
    projectRepository.save(project);

    Set<String> enabledIds = request.enabledAssessmentIds() == null ? Set.of() : request.enabledAssessmentIds();
    for (Assessment assessment : assessmentRepository.findAll()) {
      ProjectAssessment relation = new ProjectAssessment();
      relation.setProject(project);
      relation.setAssessment(assessment);
      relation.setActive(enabledIds.isEmpty() || enabledIds.contains(assessment.getTestPaperId()));
      projectAssessmentRepository.save(relation);
    }

    AppUser admin = new AppUser();
    admin.setEmail(adminEmail);
    admin.setPasswordHash(passwordEncoder.encode(request.adminPassword()));
    admin.setRole(UserRole.USER);
    userRepository.save(admin);

    ProjectMembership membership = new ProjectMembership();
    membership.setProject(project);
    membership.setUser(admin);
    membership.setRole(ProjectMembershipRole.PROJECT_ADMIN);
    membership.setEnabled(true);
    membershipRepository.save(membership);

    String inviteCode = request.initialInviteCode() == null || request.initialInviteCode().isBlank()
        ? generateInviteCode(slug)
        : request.initialInviteCode().trim().toUpperCase(Locale.ROOT);
    InvitationCode code = new InvitationCode();
    code.setProject(project);
    code.setCode(inviteCode);
    code.setMaxUses(500);
    invitationCodeRepository.save(code);

    auditService.record(principal.user(), "CREATE_PROJECT", "PROJECT", project.getSlug());
    return new CreateProjectResponse(
        new ProjectCreated(project.getSlug(), "/" + project.getSlug(), "/" + project.getSlug() + "/admin"),
        inviteCode);
  }

  @GetMapping("/projects/{slug}")
  @Transactional(readOnly = true)
  public ProjectRow project(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug) {
    projectAccessService.requirePlatformSuper(principal);
    return projectRow(projectAccessService.findProject(slug));
  }

  @PutMapping("/projects/{slug}")
  @Transactional
  public ProjectRow updateProject(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug, @Valid @RequestBody UpdateProjectRequest request) {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    project.setName(request.name().trim());
    project.setLandingTitle(request.landingTitle().trim());
    project.setLandingDescription(request.landingDescription().trim());
    project.setReportTitle(request.reportTitle().trim());
    project.setLogoUrl(request.logoUrl());
    project.setUpdatedAt(Instant.now());
    Project saved = projectRepository.save(project);
    auditService.record(principal.user(), "UPDATE_PROJECT", "PROJECT", saved.getSlug());
    return projectRow(saved);
  }

  @PatchMapping("/projects/{slug}/status")
  @Transactional
  public ProjectRow toggleStatus(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug) {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    project.setEnabled(!project.isEnabled());
    project.setUpdatedAt(Instant.now());
    Project saved = projectRepository.save(project);
    auditService.record(principal.user(), "TOGGLE_PROJECT", "PROJECT", saved.getSlug());
    return projectRow(saved);
  }

  @GetMapping("/projects/{slug}/admins")
  @Transactional(readOnly = true)
  public List<ProjectAdminRow> admins(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug) {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    return membershipRepository.findByProjectAndRole(project, ProjectMembershipRole.PROJECT_ADMIN).stream()
        .map(ProjectAdminRow::from)
        .toList();
  }

  @PostMapping("/projects/{slug}/admins")
  @Transactional
  public ProjectAdminRow createAdmin(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug, @Valid @RequestBody CreateAdminRequest request) {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    String email = normalizeEmail(request.email());
    if (userRepository.existsByEmailIgnoreCase(email)) {
      throw new ApiException(HttpStatus.CONFLICT, "该邮箱已存在");
    }
    AppUser user = new AppUser();
    user.setEmail(email);
    user.setPasswordHash(passwordEncoder.encode(request.password()));
    user.setRole(UserRole.USER);
    userRepository.save(user);

    ProjectMembership membership = new ProjectMembership();
    membership.setProject(project);
    membership.setUser(user);
    membership.setRole(ProjectMembershipRole.PROJECT_ADMIN);
    membership.setEnabled(true);
    membershipRepository.save(membership);
    auditService.record(principal.user(), "CREATE_PROJECT_ADMIN", "USER", user.getId().toString());
    return ProjectAdminRow.from(membership);
  }

  @PatchMapping("/projects/{slug}/admins/{userId}/toggle")
  @Transactional
  public ProjectAdminRow toggleAdmin(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug, @PathVariable Long userId) {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    ProjectMembership membership = membershipRepository.findByProjectAndUserId(project, userId)
        .orElseThrow(() -> new ApiException(HttpStatus.NOT_FOUND, "项目管理员不存在"));
    if (membership.getRole() != ProjectMembershipRole.PROJECT_ADMIN) {
      throw new ApiException(HttpStatus.BAD_REQUEST, "该账号不是项目管理员");
    }
    membership.setEnabled(!membership.isEnabled());
    auditService.record(principal.user(), "TOGGLE_PROJECT_ADMIN", "USER", userId.toString());
    return ProjectAdminRow.from(membershipRepository.save(membership));
  }

  @GetMapping("/projects/{slug}/dashboard")
  @Transactional(readOnly = true)
  public ProjectDashboard dashboard(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug) {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    return new ProjectDashboard(
        membershipRepository.countByProjectAndRole(project, ProjectMembershipRole.PARTICIPANT),
        submissionRepository.countByProject(project),
        reportRepository.countByProject(project),
        invitationCodeRepository.findByProject(project).size());
  }

  @GetMapping("/projects/{slug}/exports/all-data")
  @Transactional(readOnly = true)
  public ResponseEntity<byte[]> exportProjectAllData(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String slug) throws IOException {
    projectAccessService.requirePlatformSuper(principal);
    Project project = projectAccessService.findProject(slug);
    byte[] file = projectDataExportService.exportProject(project);
    auditService.record(project, principal.user(), "EXPORT_PROJECT_ALL_DATA_SUPER", "PROJECT", project.getSlug());
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.parseMediaType("application/zip"));
    headers.setContentDisposition(ContentDisposition.attachment().filename(projectDataExportService.filename(project), StandardCharsets.UTF_8).build());
    return ResponseEntity.ok().headers(headers).body(file);
  }

  private ProjectRow projectRow(Project project) {
    return new ProjectRow(
        project.getId(),
        project.getSlug(),
        project.getName(),
        project.getLandingTitle(),
        project.getLandingDescription(),
        project.getReportTitle(),
        project.getLogoUrl(),
        project.isEnabled(),
        "/" + project.getSlug(),
        "/" + project.getSlug() + "/admin",
        project.getCreatedAt().toString(),
        project.getUpdatedAt().toString());
  }

  private String normalizeSlug(String slug) {
    return slug == null ? "" : slug.trim().toLowerCase(Locale.ROOT);
  }

  private String normalizeEmail(String email) {
    return email == null ? "" : email.trim().toLowerCase(Locale.ROOT);
  }

  private String generateInviteCode(String slug) {
    String prefix = slug.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]", "-");
    for (int i = 0; i < 10; i++) {
      String suffix = UUID.randomUUID().toString().replace("-", "").substring(0, 5).toUpperCase(Locale.ROOT);
      String value = prefix + "-" + suffix;
      Project project = projectRepository.findBySlug(slug).orElse(null);
      if (project != null && !invitationCodeRepository.existsByProjectAndCodeIgnoreCase(project, value)) {
        return value;
      }
    }
    return prefix + "-" + System.currentTimeMillis();
  }

  public record ProjectRow(
      Long id,
      String slug,
      String name,
      String landingTitle,
      String landingDescription,
      String reportTitle,
      String logoUrl,
      boolean enabled,
      String url,
      String adminUrl,
      String createdAt,
      String updatedAt) {}

  public record PlatformAdminRow(Long id, String email, String role, boolean enabled, String createdAt) {
    static PlatformAdminRow from(AppUser user) {
      return new PlatformAdminRow(user.getId(), user.getEmail(), user.getRole().name(), user.isEnabled(), user.getCreatedAt().toString());
    }
  }

  public record ProjectDashboard(long users, long submissions, long reports, long invitationCodes) {}
  public record ProjectAdminRow(Long userId, String email, boolean enabled, String createdAt) {
    static ProjectAdminRow from(ProjectMembership membership) {
      return new ProjectAdminRow(
          membership.getUser().getId(),
          membership.getUser().getEmail(),
          membership.isEnabled(),
          membership.getCreatedAt().toString());
    }
  }

  public record CreateProjectRequest(
      @NotBlank String name,
      @NotBlank String slug,
      @NotBlank String landingTitle,
      @NotBlank String landingDescription,
      @NotBlank String reportTitle,
      String logoUrl,
      @NotBlank @Email String adminEmail,
      @NotBlank @Size(min = 8) String adminPassword,
      Set<String> enabledAssessmentIds,
      String initialInviteCode) {}

  public record UpdateProjectRequest(
      @NotBlank String name,
      @NotBlank String landingTitle,
      @NotBlank String landingDescription,
      @NotBlank String reportTitle,
      String logoUrl) {}

  public record CreateAdminRequest(@NotBlank @Email String email, @NotBlank @Size(min = 8) String password) {}
  public record CreatePlatformAdminRequest(@NotBlank @Email String email, @NotBlank @Size(min = 8) String password) {}
  public record ProjectCreated(String slug, String url, String adminUrl) {}
  public record CreateProjectResponse(ProjectCreated project, String initialInviteCode) {}
}
