package com.davos.assessment.assessment;

import com.davos.assessment.audit.AuditService;
import com.davos.assessment.auth.UserPrincipal;
import com.davos.assessment.common.ApiException;
import com.davos.assessment.profile.ProfileRepository;
import com.davos.assessment.project.Project;
import com.davos.assessment.project.ProjectAccessService;
import com.davos.assessment.project.ProjectAssessment;
import com.davos.assessment.project.ProjectAssessmentRepository;
import com.davos.assessment.report.Report;
import com.davos.assessment.report.ReportRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping({"/api/assessment", "/api/projects/{slug}/assessment"})
public class AssessmentController {
  private final AssessmentRepository assessmentRepository;
  private final QuestionRepository questionRepository;
  private final SubmissionRepository submissionRepository;
  private final ReportRepository reportRepository;
  private final ProfileRepository profileRepository;
  private final UserAssessmentAccessRepository userAssessmentAccessRepository;
  private final AssessmentProgressRepository assessmentProgressRepository;
  private final ProjectAssessmentRepository projectAssessmentRepository;
  private final ProjectAccessService projectAccessService;
  private final ScoringService scoringService;
  private final ObjectMapper objectMapper;
  private final AuditService auditService;
  private final JdbcTemplate jdbcTemplate;

  public AssessmentController(
      AssessmentRepository assessmentRepository,
      QuestionRepository questionRepository,
      SubmissionRepository submissionRepository,
      ReportRepository reportRepository,
      ProfileRepository profileRepository,
      UserAssessmentAccessRepository userAssessmentAccessRepository,
      AssessmentProgressRepository assessmentProgressRepository,
      ProjectAssessmentRepository projectAssessmentRepository,
      ProjectAccessService projectAccessService,
      ScoringService scoringService,
      ObjectMapper objectMapper,
      AuditService auditService,
      JdbcTemplate jdbcTemplate) {
    this.assessmentRepository = assessmentRepository;
    this.questionRepository = questionRepository;
    this.submissionRepository = submissionRepository;
    this.reportRepository = reportRepository;
    this.profileRepository = profileRepository;
    this.userAssessmentAccessRepository = userAssessmentAccessRepository;
    this.assessmentProgressRepository = assessmentProgressRepository;
    this.projectAssessmentRepository = projectAssessmentRepository;
    this.projectAccessService = projectAccessService;
    this.scoringService = scoringService;
    this.objectMapper = objectMapper;
    this.auditService = auditService;
    this.jdbcTemplate = jdbcTemplate;
  }

  @GetMapping("/current")
  public AssessmentResponse current(@AuthenticationPrincipal UserPrincipal principal) {
    Project project = projectAccessService.requireProjectParticipant(projectAccessService.currentSlugOrDefault(), principal);
    Assessment assessment = currentAssessment(project);
    return response(project, assessment, principal.user());
  }

  @GetMapping("/list")
  public List<AssessmentResponse> list(@AuthenticationPrincipal UserPrincipal principal) {
    Project project = projectAccessService.requireProjectParticipant(projectAccessService.currentSlugOrDefault(), principal);
    return projectAssessmentRepository.findByProjectAndActiveTrue(project).stream()
        .map(ProjectAssessment::getAssessment)
        .filter(Assessment::isActive)
        .map(item -> response(project, item, principal.user()))
        .toList();
  }

  private AssessmentResponse response(Project project, Assessment assessment) {
    return response(project, assessment, null);
  }

  private AssessmentResponse response(Project project, Assessment assessment, com.davos.assessment.user.AppUser user) {
    List<QuestionResponse> questions = normalizeQuestions(assessment, questionRepository.findByAssessmentOrderBySortOrderAsc(assessment)).stream()
        .map(QuestionResponse::from)
        .toList();
    boolean completed = false;
    boolean canRetake = false;
    if (user != null) {
      completed = submissionRepository.countByProjectAndUserAndAssessment(project, user, assessment) > 0;
      canRetake = userAssessmentAccessRepository.findByProjectAndUserAndAssessment(project, user, assessment)
          .map(UserAssessmentAccess::isCanRetake)
          .orElse(false);
    }
    return new AssessmentResponse(assessment.getTestPaperId(), assessment.getTitle(), assessment.getDescription(), questions, completed, canRetake);
  }

  @PostMapping("/submit")
  @Transactional
  public SubmitResponse submit(@AuthenticationPrincipal UserPrincipal principal, @Valid @RequestBody SubmitRequest request) throws JsonProcessingException {
    Project project = projectAccessService.requireProjectParticipant(projectAccessService.currentSlugOrDefault(), principal);
    profileRepository.findByProjectAndUser(project, principal.user())
        .orElseThrow(() -> new ApiException(HttpStatus.BAD_REQUEST, "请先完善个人信息"));

    Assessment assessment = assessmentRepository.findByTestPaperId(request.testPaperId())
        .orElseThrow(() -> new ApiException(HttpStatus.NOT_FOUND, "测评不存在"));
    requireProjectAssessment(project, assessment);
    lockSubmissionSlot(project.getId(), principal.user().getId(), assessment.getId());
    boolean hasCompleted = submissionRepository.countByProjectAndUserAndAssessment(project, principal.user(), assessment) > 0;
    UserAssessmentAccess access = userAssessmentAccessRepository.findByProjectAndUserAndAssessment(project, principal.user(), assessment).orElse(null);
    boolean canRetake = access != null && access.isCanRetake();
    if (hasCompleted && !canRetake) {
      throw new ApiException(HttpStatus.CONFLICT, "该量表已完成，如需重答请联系管理员开放");
    }
    List<Question> questions = normalizeQuestions(assessment, questionRepository.findByAssessmentOrderBySortOrderAsc(assessment));
    Map<String, Integer> answerMap = request.answers().stream()
        .collect(Collectors.toMap(AnswerRequest::questionCode, AnswerRequest::value, (a, b) -> b));

    for (Question question : questions) {
      if (!answerMap.containsKey(question.getCode())) {
        throw new ApiException(HttpStatus.BAD_REQUEST, "还有题目未完成");
      }
    }

    ScoringService.ScoringResult result = scoringService.score(assessment, questions, answerMap);

    Submission submission = new Submission();
    submission.setProject(project);
    submission.setUser(principal.user());
    submission.setAssessment(assessment);
    submission.setRawScore(result.rawScore());
    submission.setLevel(result.level());
    submission.setDimensionScores(objectMapper.writeValueAsString(result.dimensionScores()));
    submission.setSubmittedAt(Instant.now());
    submissionRepository.save(submission);

    batchInsertAnswers(submission.getId(), questions, answerMap);

    Report report = new Report();
    report.setProject(project);
    report.setSubmission(submission);
    report.setUser(principal.user());
    report.setSummary(result.summary());
    report.setSuggestions(result.suggestions());
    reportRepository.save(report);
    assessmentProgressRepository.findByProjectAndUserAndAssessment(project, principal.user(), assessment).ifPresent(assessmentProgressRepository::delete);
    if (access != null && access.isCanRetake()) {
      access.setCanRetake(false);
      userAssessmentAccessRepository.save(access);
    }

    auditService.record(project, principal.user(), "SUBMIT_ASSESSMENT", "SUBMISSION", submission.getId().toString());
    return new SubmitResponse(submission.getId(), report.getId(), result.rawScore(), result.level(), result.dimensionScores(), result.summary(), result.suggestions());
  }

  private void batchInsertAnswers(Long submissionId, List<Question> questions, Map<String, Integer> answerMap) {
    final String sql = "insert into answers(submission_id, question_id, answer_value) values (?, ?, ?)";
    jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
      @Override
      public void setValues(java.sql.PreparedStatement ps, int i) throws java.sql.SQLException {
        Question question = questions.get(i);
        ps.setLong(1, submissionId);
        ps.setLong(2, question.getId());
        ps.setInt(3, answerMap.get(question.getCode()));
      }

      @Override
      public int getBatchSize() {
        return questions.size();
      }
    });
  }

  private void lockSubmissionSlot(Long projectId, Long userId, Long assessmentId) {
    jdbcTemplate.query(
        "select pg_advisory_xact_lock(?, ?)",
        ps -> {
          ps.setInt(1, Long.hashCode((projectId == null ? 0L : projectId) * 31L + (userId == null ? 0L : userId)));
          ps.setInt(2, Long.hashCode(assessmentId == null ? 0L : assessmentId));
        },
        rs -> null);
  }

  @GetMapping("/progress/{testPaperId}")
  @Transactional(readOnly = true)
  public ProgressResponse progress(@AuthenticationPrincipal UserPrincipal principal, @PathVariable String testPaperId) {
    Project project = projectAccessService.requireProjectParticipant(projectAccessService.currentSlugOrDefault(), principal);
    Assessment assessment = assessmentRepository.findByTestPaperId(testPaperId)
        .orElseThrow(() -> new ApiException(HttpStatus.NOT_FOUND, "测评不存在"));
    requireProjectAssessment(project, assessment);
    return assessmentProgressRepository.findByProjectAndUserAndAssessment(project, principal.user(), assessment)
        .map(item -> new ProgressResponse(item.getAnswersJson(), item.getUpdatedAt().toString()))
        .orElse(new ProgressResponse("{}", ""));
  }

  @PutMapping("/progress")
  @Transactional
  public ProgressResponse saveProgress(
      @AuthenticationPrincipal UserPrincipal principal,
      @Valid @RequestBody ProgressSaveRequest request) throws JsonProcessingException {
    Assessment assessment = assessmentRepository.findByTestPaperId(request.testPaperId())
        .orElseThrow(() -> new ApiException(HttpStatus.NOT_FOUND, "测评不存在"));
    Project project = projectAccessService.requireProjectParticipant(projectAccessService.currentSlugOrDefault(), principal);
    requireProjectAssessment(project, assessment);
    boolean hasCompleted = submissionRepository.countByProjectAndUserAndAssessment(project, principal.user(), assessment) > 0;
    if (hasCompleted) {
      return new ProgressResponse("{}", "");
    }
    String json = objectMapper.writeValueAsString(request.answers());
    AssessmentProgress progress = assessmentProgressRepository.findByProjectAndUserAndAssessment(project, principal.user(), assessment).orElseGet(() -> {
      AssessmentProgress item = new AssessmentProgress();
      item.setProject(project);
      item.setUser(principal.user());
      item.setAssessment(assessment);
      return item;
    });
    progress.setAnswersJson(json);
    progress.setUpdatedAt(Instant.now());
    assessmentProgressRepository.save(progress);
    return new ProgressResponse(progress.getAnswersJson(), progress.getUpdatedAt().toString());
  }

  private Assessment currentAssessment(Project project) {
    return projectAssessmentRepository.findByProjectAndActiveTrue(project).stream()
        .map(ProjectAssessment::getAssessment)
        .filter(Assessment::isActive)
        .findFirst()
        .orElseThrow(() -> new ApiException(HttpStatus.NOT_FOUND, "暂无启用测评"));
  }

  private void requireProjectAssessment(Project project, Assessment assessment) {
    boolean enabled = projectAssessmentRepository.findByProjectAndAssessment(project, assessment)
        .filter(ProjectAssessment::isActive)
        .isPresent() && assessment.isActive();
    if (!enabled) {
      throw new ApiException(HttpStatus.NOT_FOUND, "该项目未启用此量表");
    }
  }

  private List<Question> normalizeQuestions(Assessment assessment, List<Question> questions) {
    String paperId = assessment.getTestPaperId();
    if (paperId.contains("2058")) {
      return questions.stream().filter(q -> codeSuffixBetween(q.getCode(), 1, 88)).toList();
    }
    if (paperId.contains("2078")) {
      return questions.stream().filter(q -> codeSuffixBetween(q.getCode(), 1, 90)).toList();
    }
    if (paperId.contains("2082") || paperId.contains("2083")) {
      return questions.stream().filter(q -> codeSuffixBetween(q.getCode(), 1, 20)).toList();
    }
    if (paperId.contains("2084")) {
      return questions.stream().filter(q -> codeSuffixBetween(q.getCode(), 1, 29)).toList();
    }
    return questions;
  }

  private boolean codeSuffixBetween(String code, int min, int max) {
    if (code == null || code.length() < 3) return false;
    try {
      int suffix = Integer.parseInt(code.substring(code.length() - 3));
      return suffix >= min && suffix <= max;
    } catch (NumberFormatException ex) {
      return false;
    }
  }

  public record AssessmentResponse(String testPaperId, String title, String description, List<QuestionResponse> questions, boolean completed, boolean canRetake) {}
  public record QuestionResponse(Long id, String code, String dimension, String prompt, int sortOrder) {
    static QuestionResponse from(Question question) {
      return new QuestionResponse(question.getId(), question.getCode(), question.getDimension(), question.getPrompt(), question.getSortOrder());
    }
  }
  public record SubmitRequest(String testPaperId, @NotEmpty List<AnswerRequest> answers) {}
  public record AnswerRequest(String questionCode, @Min(1) @Max(5) int value) {}
  public record SubmitResponse(Long submissionId, Long reportId, int rawScore, String level, Map<String, Integer> dimensionScores, String summary, String suggestions) {}
  public record ProgressSaveRequest(String testPaperId, Map<String, Integer> answers) {}
  public record ProgressResponse(String answersJson, String updatedAt) {}
}
