package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.JamData;
import com.pandoli365.bibimbap.jam.JamEvalWindow;
import com.pandoli365.bibimbap.mapper.JamEntriesMapper;
import com.pandoli365.bibimbap.mapper.JamVotesMapper;
import com.pandoli365.bibimbap.mapper.JamsMapper;
import com.pandoli365.bibimbap.security.CsrfTokens;
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.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

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

@Controller
public class JamVoteController {

    private final JamsMapper jamsMapper;
    private final JamEntriesMapper jamEntriesMapper;
    private final JamVotesMapper jamVotesMapper;

    public JamVoteController(
            JamsMapper jamsMapper,
            JamEntriesMapper jamEntriesMapper,
            JamVotesMapper jamVotesMapper
    ) {
        this.jamsMapper = jamsMapper;
        this.jamEntriesMapper = jamEntriesMapper;
        this.jamVotesMapper = jamVotesMapper;
    }

    @PostMapping("/jams/{slug}/vote")
    @ResponseBody
    @Transactional
    public ResponseEntity<Map<String, Object>> vote(
            @PathVariable("slug") String slug,
            @RequestParam("gameId") long gameId,
            HttpServletRequest request,
            HttpSession session
    ) {
        // 게이트 1 — CSRF
        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 — 잼 존재
        JamData jam = jamsMapper.getBySlug(slug);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "잼을 찾을 수 없습니다.");
        }
        // 게이트 4 — 평가 기간
        if (!JamEvalWindow.isOpen(jam, OffsetDateTime.now())) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "투표 기간이 아닙니다.");
        }
        // 게이트 5 — 출품작 존재
        if (!jamEntriesMapper.exists(jam.getId(), gameId)) {
            return response(HttpStatus.NOT_FOUND, "출품작을 찾을 수 없습니다.");
        }

        // 토글: 미투표→신규, 동일 대상→멱등 no-op, 다른 대상→교체
        Long current = jamVotesMapper.findVotedGameId(jam.getId(), userId);
        boolean changed;
        if (current == null) {
            jamVotesMapper.castVote(jam.getId(), gameId, userId);
            changed = false;
        } else if (current.longValue() == gameId) {
            changed = false;
        } else {
            jamVotesMapper.updateVote(jam.getId(), userId, gameId);
            changed = true;
        }

        // 진행 중에는 집계를 노출하지 않는다(밴드왜건 회피) — 본인 표만 반환
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", 200);
        body.put("votedGameId", gameId);
        body.put("changed", changed);
        return ResponseEntity.ok(body);
    }

    @DeleteMapping("/jams/{slug}/vote")
    @ResponseBody
    @Transactional
    public ResponseEntity<Map<String, Object>> cancelVote(
            @PathVariable("slug") String slug,
            HttpServletRequest request,
            HttpSession session
    ) {
        // 게이트 1 — CSRF
        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 — 잼 존재
        JamData jam = jamsMapper.getBySlug(slug);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "잼을 찾을 수 없습니다.");
        }
        // 게이트 4 — 평가 기간
        if (!JamEvalWindow.isOpen(jam, OffsetDateTime.now())) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "투표 기간이 아닙니다.");
        }

        int affected = jamVotesMapper.deleteVote(jam.getId(), userId);
        if (affected == 0) {
            return response(HttpStatus.NOT_FOUND, "취소할 표가 없습니다.");
        }

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

    @GetMapping("/jams/{slug}/vote/mine")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> myVote(
            @PathVariable("slug") String slug,
            HttpSession session
    ) {
        Long userId = sessionUserId(session);
        if (userId == null) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }
        JamData jam = jamsMapper.getBySlug(slug);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "잼을 찾을 수 없습니다.");
        }

        // 본인 표는 진행 중에도 노출
        Long votedGameId = jamVotesMapper.findVotedGameId(jam.getId(), userId);

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

    @GetMapping("/jams/{slug}/vote/results")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> results(
            @PathVariable("slug") String slug
    ) {
        JamData jam = jamsMapper.getBySlug(slug);
        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;

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", 200);
        if (publiclyVisible) {
            List<Map<String, Object>> results = jamVotesMapper.listCountsByJam(jam.getId());
            long total = 0;
            for (Map<String, Object> row : results) {
                total += ((Number) row.get("voteCount")).longValue();
            }
            body.put("results", results);
            body.put("total", total);
        } else {
            body.put("open", true);
            body.put("results", null);
            body.put("total", jamVotesMapper.countByJam(jam.getId()));
        }
        return ResponseEntity.ok(body);
    }

    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);
    }
}
