package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.JamCriterionData;
import com.pandoli365.bibimbap.data.JamData;
import com.pandoli365.bibimbap.data.JamScoreData;
import com.pandoli365.bibimbap.jam.JamEvalWindow;
import com.pandoli365.bibimbap.mapper.JamCriteriaMapper;
import com.pandoli365.bibimbap.mapper.JamEntriesMapper;
import com.pandoli365.bibimbap.mapper.JamScoreStatsMapper;
import com.pandoli365.bibimbap.mapper.JamScoresMapper;
import com.pandoli365.bibimbap.mapper.JamsMapper;
import com.pandoli365.bibimbap.security.CsrfTokens;
import com.pandoli365.bibimbap.security.JamRoleGate;
import com.pandoli365.bibimbap.security.PermissionGate;
import com.pandoli365.bibimbap.security.PermissionKeys;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Controller
public class JamScoringController {

    private final JamsMapper jamsMapper;
    private final JamEntriesMapper jamEntriesMapper;
    private final JamCriteriaMapper jamCriteriaMapper;
    private final JamScoresMapper jamScoresMapper;
    private final JamScoreStatsMapper jamScoreStatsMapper;
    private final JamRoleGate jamRoleGate;
    private final PermissionGate permissionGate;

    public JamScoringController(
            JamsMapper jamsMapper,
            JamEntriesMapper jamEntriesMapper,
            JamCriteriaMapper jamCriteriaMapper,
            JamScoresMapper jamScoresMapper,
            JamScoreStatsMapper jamScoreStatsMapper,
            JamRoleGate jamRoleGate,
            PermissionGate permissionGate
    ) {
        this.jamsMapper = jamsMapper;
        this.jamEntriesMapper = jamEntriesMapper;
        this.jamCriteriaMapper = jamCriteriaMapper;
        this.jamScoresMapper = jamScoresMapper;
        this.jamScoreStatsMapper = jamScoreStatsMapper;
        this.jamRoleGate = jamRoleGate;
        this.permissionGate = permissionGate;
    }

    @RequestMapping(value = "/jams/{jamId}/games/{gameId}/scores",
            method = {RequestMethod.POST, RequestMethod.PUT})
    @ResponseBody
    @Transactional
    public ResponseEntity<Map<String, Object>> submitScores(
            @PathVariable long jamId,
            @PathVariable long gameId,
            @RequestBody Map<String, Object> body,
            HttpServletRequest request,
            HttpSession session
    ) {
        // 게이트 1 — CSRF (난제1 순서: 가장 먼저)
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        // 게이트 2 — 인증
        Long userId = sessionUserId(session);
        if (userId == null) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }
        // 게이트 3 — 심사 자격 (W2-2)
        if (!jamRoleGate.isJudge(session, jamId)) {
            return response(HttpStatus.FORBIDDEN, "심사 권한이 없습니다.");
        }
        // 게이트 4 — 잼 존재
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "잼을 찾을 수 없습니다.");
        }
        // 게이트 5 — 평가 기간 (W2-3 F6)
        if (!JamEvalWindow.isOpen(jam, OffsetDateTime.now())) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "평가 기간이 아닙니다.");
        }
        // 게이트 6 — 출품작 존재
        if (!jamEntriesMapper.exists(jamId, gameId)) {
            return response(HttpStatus.NOT_FOUND, "출품작을 찾을 수 없습니다.");
        }
        // 게이트 7 — 자기 출품작 차단 (W2-2)
        if (jamRoleGate.isOwnEntry(jamId, gameId, userId)) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "자기 출품작은 심사할 수 없습니다.");
        }

        // 게이트 8 — body 파싱 + 전수 검증 (통과 후에만 UPSERT, 부분 저장 금지)
        Object rawScores = body == null ? null : body.get("scores");
        if (!(rawScores instanceof List<?> scoreList) || scoreList.isEmpty()) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "입력된 점수가 없습니다.");
        }

        Set<String> allowedKeys = new HashSet<>();
        for (JamCriterionData criterion : jamCriteriaMapper.listByJam(jamId)) {
            allowedKeys.add(criterion.getCriterionKey());
        }

        // 검증과 변환을 먼저 전수 수행 — 1건이라도 실패하면 upsert 미호출
        Map<String, Integer> validated = new LinkedHashMap<>();
        for (Object element : scoreList) {
            if (!(element instanceof Map<?, ?> entry)) {
                return response(HttpStatus.UNPROCESSABLE_ENTITY, "점수 항목 형식이 올바르지 않습니다.");
            }
            String criterionKey = String.valueOf(entry.get("criterionKey"));
            if (!allowedKeys.contains(criterionKey)) {
                return response(HttpStatus.UNPROCESSABLE_ENTITY, "등록되지 않은 평가 기준입니다.");
            }
            Integer score = parseScore(entry.get("score"));
            if (score == null || score < 1 || score > 5) {
                return response(HttpStatus.UNPROCESSABLE_ENTITY, "점수는 1~5 사이여야 합니다.");
            }
            validated.put(criterionKey, score);
        }

        int savedCount = 0;
        for (Map.Entry<String, Integer> e : validated.entrySet()) {
            jamScoresMapper.upsertScore(jamId, gameId, userId, e.getKey(), e.getValue());
            savedCount++;
        }

        Map<String, Object> success = new LinkedHashMap<>();
        success.put("status", 200);
        success.put("message", "점수가 저장되었습니다.");
        success.put("jamId", jamId);
        success.put("gameId", gameId);
        success.put("savedCount", savedCount);
        return ResponseEntity.ok(success);
    }

    @GetMapping("/jams/{jamId}/games/{gameId}/scores/mine")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> myScores(
            @PathVariable long jamId,
            @PathVariable long gameId,
            HttpSession session
    ) {
        Long userId = sessionUserId(session);
        if (userId == null) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }
        if (!jamRoleGate.isJudge(session, jamId)) {
            return response(HttpStatus.FORBIDDEN, "심사 권한이 없습니다.");
        }

        List<JamScoreData> mine = jamScoresMapper.listByJudge(jamId, gameId, userId);
        List<JamCriterionData> criteria = jamCriteriaMapper.listByJam(jamId);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("status", 200);
        result.put("scores", mine);
        result.put("criteria", criteria);
        return ResponseEntity.ok(result);
    }

    @GetMapping("/jams/{jamId}/scores/summary")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> summary(
            @PathVariable long jamId,
            HttpSession session
    ) {
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "잼을 찾을 수 없습니다.");
        }

        boolean evalEnded = jam.getEvalEndAt() != null
                && OffsetDateTime.now().isAfter(jam.getEvalEndAt());
        boolean publiclyVisible = "CLOSED".equals(jam.getStatus()) || evalEnded;
        if (!publiclyVisible
                && !permissionGate.has(session, PermissionKeys.GAME_JAM_MANAGE.name())) {
            return response(HttpStatus.FORBIDDEN, "집계는 평가 종료 후 공개됩니다.");
        }

        List<Map<String, Object>> stats = jamScoreStatsMapper.listStatsByJam(jamId);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("status", 200);
        result.put("stats", stats);
        return ResponseEntity.ok(result);
    }

    @GetMapping("/jams/{jamId}/games/{gameId}/score")
    public String scoringForm(
            @PathVariable long jamId,
            @PathVariable long gameId,
            HttpServletRequest request,
            HttpSession session,
            Model model
    ) {
        Long userId = sessionUserId(session);
        if (userId == null) {
            return "redirect:/login";
        }
        if (!jamRoleGate.isJudge(session, jamId)) {
            return "redirect:/jams";
        }
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return "redirect:/jams";
        }

        model.addAttribute("jam", jam);
        model.addAttribute("jamId", jamId);
        model.addAttribute("gameId", gameId);
        model.addAttribute("criteria", jamCriteriaMapper.listByJam(jamId));
        model.addAttribute("mine", jamScoresMapper.listByJudge(jamId, gameId, userId));
        model.addAttribute("csrfToken", CsrfTokens.getOrCreate(request.getSession()));
        return "jam-scoring";
    }

    private Integer parseScore(Object raw) {
        if (raw instanceof Number number) {
            return number.intValue();
        }
        if (raw instanceof String text) {
            try {
                return Integer.parseInt(text.trim());
            } catch (NumberFormatException e) {
                return null;
            }
        }
        return null;
    }

    private Long sessionUserId(HttpSession session) {
        if (session == null) {
            return null;
        }
        Object userId = session.getAttribute("userId");
        if (userId instanceof Number number) {
            return number.longValue();
        }
        if (userId instanceof String text) {
            try {
                return Long.parseLong(text);
            } catch (NumberFormatException e) {
                return null;
            }
        }
        return null;
    }

    private ResponseEntity<Map<String, Object>> response(HttpStatus status, String message) {
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", status.value());
        body.put("message", message);
        return ResponseEntity.status(status).body(body);
    }
}
