package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.JamData;
import com.pandoli365.bibimbap.jam.JamLifecycle;
import com.pandoli365.bibimbap.jam.JamSlugs;
import com.pandoli365.bibimbap.jam.JamStatus;
import com.pandoli365.bibimbap.mapper.JamStatusLogMapper;
import com.pandoli365.bibimbap.mapper.JamsMapper;
import com.pandoli365.bibimbap.security.CsrfTokens;
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.dao.DuplicateKeyException;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import java.util.LinkedHashMap;
import java.util.Map;

@Controller
public class JamAdminController {

    private static final int MAX_TITLE = 200;
    private static final int MAX_DESCRIPTION = 5000;
    private static final int MAX_DISCORD_URL = 500;
    private static final int MAX_PRIZE_INFO = 5000;
    private static final int MAX_SPONSOR_INFO = 5000;
    private static final int MAX_SLUG_ATTEMPTS = 20;

    private final JamsMapper jamsMapper;
    private final JamStatusLogMapper jamStatusLogMapper;
    private final PermissionGate gate;
    private final JamLifecycle jamLifecycle;

    public JamAdminController(JamsMapper jamsMapper,
                              JamStatusLogMapper jamStatusLogMapper,
                              PermissionGate gate,
                              JamLifecycle jamLifecycle) {
        this.jamsMapper = jamsMapper;
        this.jamStatusLogMapper = jamStatusLogMapper;
        this.gate = gate;
        this.jamLifecycle = jamLifecycle;
    }

    @GetMapping("/admin/jams")
    public String console(Model model, HttpServletRequest request, HttpSession session) {
        if (!gate.isAuthenticated(session)) {
            return "redirect:/login";
        }
        if (!gate.has(session, PermissionKeys.GAME_JAM_MANAGE.name())) {
            return "redirect:/";
        }
        model.addAttribute("jams", jamsMapper.listAllForAdmin());
        model.addAttribute("csrfToken", CsrfTokens.getOrCreate(request.getSession()));
        return "admin-jam-list";
    }

    @PostMapping("/admin/jams")
    @Transactional
    public ResponseEntity<Map<String, Object>> create(
            @RequestParam(name = "title", required = false) String title,
            @RequestParam(name = "description", required = false) String description,
            @RequestParam(name = "recruitStartAt", required = false) String recruitStartAt,
            @RequestParam(name = "devStartAt", required = false) String devStartAt,
            @RequestParam(name = "evalStartAt", required = false) String evalStartAt,
            @RequestParam(name = "evalEndAt", required = false) String evalEndAt,
            @RequestParam(name = "discordUrl", required = false) String discordUrl,
            @RequestParam(name = "prizeInfo", required = false) String prizeInfo,
            @RequestParam(name = "sponsorInfo", required = false) String sponsorInfo,
            HttpServletRequest request,
            HttpSession session) {
        ResponseEntity<Map<String, Object>> denied = requireJamManage(session);
        if (denied != null) {
            return denied;
        }
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }

        String normalizedTitle = trimToNull(title);
        if (normalizedTitle == null || normalizedTitle.length() > MAX_TITLE) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "제목을 200자 이내로 입력해 주세요.");
        }
        String normalizedDescription = trimToNull(description);
        if (normalizedDescription != null && normalizedDescription.length() > MAX_DESCRIPTION) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "상세 설명이 너무 깁니다.");
        }
        String normalizedDiscordUrl = trimToNull(discordUrl);
        if (normalizedDiscordUrl != null && normalizedDiscordUrl.length() > MAX_DISCORD_URL) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "디스코드 URL이 너무 깁니다.");
        }
        String normalizedPrizeInfo = trimToNull(prizeInfo);
        if (normalizedPrizeInfo != null && normalizedPrizeInfo.length() > MAX_PRIZE_INFO) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "시상 정보가 너무 깁니다.");
        }
        String normalizedSponsorInfo = trimToNull(sponsorInfo);
        if (normalizedSponsorInfo != null && normalizedSponsorInfo.length() > MAX_SPONSOR_INFO) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "후원 정보가 너무 깁니다.");
        }

        OffsetDateTime recruitStart;
        OffsetDateTime devStart;
        OffsetDateTime evalStart;
        OffsetDateTime evalEnd;
        try {
            recruitStart = parseOffset(recruitStartAt);
            devStart = parseOffset(devStartAt);
            evalStart = parseOffset(evalStartAt);
            evalEnd = parseOffset(evalEndAt);
        } catch (DateTimeParseException e) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "기간 형식을 확인해 주세요.");
        }
        if (evalStart != null && evalEnd != null && evalStart.isAfter(evalEnd)) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "평가 종료가 시작보다 빠를 수 없습니다.");
        }

        Long actorId = sessionUserId(session);
        JamData jam = new JamData();
        jam.setTitle(normalizedTitle);
        jam.setDescription(normalizedDescription);
        jam.setRecruitStartAt(recruitStart);
        jam.setDevStartAt(devStart);
        jam.setEvalStartAt(evalStart);
        jam.setEvalEndAt(evalEnd);
        jam.setDiscordUrl(normalizedDiscordUrl);
        jam.setPrizeInfo(normalizedPrizeInfo);
        jam.setSponsorInfo(normalizedSponsorInfo);
        jam.setIsVisible(true);
        jam.setStatus(JamStatus.RECRUIT.name());
        jam.setCreatedBy(actorId);

        String baseSlug = JamSlugs.generate(normalizedTitle);
        boolean inserted = false;
        jam.setSlug(baseSlug);
        // slug 활성 유니크(ux_jams_slug_active) 충돌 시 -2, -3, ... 접미사로 재시도
        for (int attempt = 1; attempt <= MAX_SLUG_ATTEMPTS && !inserted; attempt++) {
            if (attempt > 1) {
                jam.setSlug(JamSlugs.withSuffix(baseSlug, attempt));
            }
            try {
                jamsMapper.insertJam(jam);
                inserted = true;
            } catch (DuplicateKeyException e) {
                jam.setId(null);
            }
        }
        if (!inserted || jam.getId() == null) {
            return response(HttpStatus.INTERNAL_SERVER_ERROR, "게임잼 식별자 생성에 실패했습니다.");
        }

        jamStatusLogMapper.insert(jam.getId(), null, JamStatus.RECRUIT.name(), actorId, "MANUAL");

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", HttpStatus.OK.value());
        body.put("message", "게임잼을 생성했습니다.");
        body.put("jamId", jam.getId());
        body.put("slug", jam.getSlug());
        return ResponseEntity.ok(body);
    }

    @PostMapping("/admin/jams/{jamId}")
    @Transactional
    public ResponseEntity<Map<String, Object>> update(
            @PathVariable("jamId") long jamId,
            @RequestParam(name = "title", required = false) String title,
            @RequestParam(name = "description", required = false) String description,
            @RequestParam(name = "recruitStartAt", required = false) String recruitStartAt,
            @RequestParam(name = "devStartAt", required = false) String devStartAt,
            @RequestParam(name = "evalStartAt", required = false) String evalStartAt,
            @RequestParam(name = "evalEndAt", required = false) String evalEndAt,
            @RequestParam(name = "discordUrl", required = false) String discordUrl,
            @RequestParam(name = "prizeInfo", required = false) String prizeInfo,
            @RequestParam(name = "sponsorInfo", required = false) String sponsorInfo,
            HttpServletRequest request,
            HttpSession session) {
        ResponseEntity<Map<String, Object>> denied = requireJamManage(session);
        if (denied != null) {
            return denied;
        }
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "게임잼을 찾을 수 없습니다.");
        }

        String normalizedTitle = trimToNull(title);
        if (normalizedTitle == null || normalizedTitle.length() > MAX_TITLE) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "제목을 200자 이내로 입력해 주세요.");
        }
        String normalizedDescription = trimToNull(description);
        if (normalizedDescription != null && normalizedDescription.length() > MAX_DESCRIPTION) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "상세 설명이 너무 깁니다.");
        }
        String normalizedDiscordUrl = trimToNull(discordUrl);
        if (normalizedDiscordUrl != null && normalizedDiscordUrl.length() > MAX_DISCORD_URL) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "디스코드 URL이 너무 깁니다.");
        }
        String normalizedPrizeInfo = trimToNull(prizeInfo);
        if (normalizedPrizeInfo != null && normalizedPrizeInfo.length() > MAX_PRIZE_INFO) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "시상 정보가 너무 깁니다.");
        }
        String normalizedSponsorInfo = trimToNull(sponsorInfo);
        if (normalizedSponsorInfo != null && normalizedSponsorInfo.length() > MAX_SPONSOR_INFO) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "후원 정보가 너무 깁니다.");
        }

        OffsetDateTime recruitStart;
        OffsetDateTime devStart;
        OffsetDateTime evalStart;
        OffsetDateTime evalEnd;
        try {
            recruitStart = parseOffset(recruitStartAt);
            devStart = parseOffset(devStartAt);
            evalStart = parseOffset(evalStartAt);
            evalEnd = parseOffset(evalEndAt);
        } catch (DateTimeParseException e) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "기간 형식을 확인해 주세요.");
        }
        if (evalStart != null && evalEnd != null && evalStart.isAfter(evalEnd)) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "평가 종료가 시작보다 빠를 수 없습니다.");
        }

        jam.setId(jamId);
        jam.setTitle(normalizedTitle);
        jam.setDescription(normalizedDescription);
        jam.setRecruitStartAt(recruitStart);
        jam.setDevStartAt(devStart);
        jam.setEvalStartAt(evalStart);
        jam.setEvalEndAt(evalEnd);
        jam.setDiscordUrl(normalizedDiscordUrl);
        jam.setPrizeInfo(normalizedPrizeInfo);
        jam.setSponsorInfo(normalizedSponsorInfo);
        jamsMapper.updateJam(jam);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", HttpStatus.OK.value());
        body.put("message", "게임잼을 수정했습니다.");
        body.put("jamId", jamId);
        return ResponseEntity.ok(body);
    }

    @PostMapping("/admin/jams/{jamId}/status")
    @Transactional
    public ResponseEntity<Map<String, Object>> changeStatus(
            @PathVariable("jamId") long jamId,
            @RequestParam("toStatus") String toStatus,
            HttpServletRequest request,
            HttpSession session) {
        ResponseEntity<Map<String, Object>> denied = requireJamManage(session);
        if (denied != null) {
            return denied;
        }
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "게임잼을 찾을 수 없습니다.");
        }

        JamStatus to = JamStatus.from(toStatus);
        if (to == null) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "알 수 없는 상태입니다.");
        }
        JamStatus from = JamStatus.from(jam.getStatus());
        if (!jamLifecycle.isAllowed(from, to)) {
            return response(HttpStatus.CONFLICT, "허용되지 않는 전이입니다.");
        }
        if (!jamLifecycle.isPeriodReady(to, jam)) {
            return response(HttpStatus.UNPROCESSABLE_ENTITY, "전이에 필요한 기간 필드가 설정되지 않았습니다.");
        }

        Long actorId = sessionUserId(session);
        jamsMapper.updateStatus(jamId, to.name());
        jamStatusLogMapper.insert(jamId, jam.getStatus(), to.name(), actorId, "MANUAL");

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", HttpStatus.OK.value());
        body.put("message", "상태를 전이했습니다.");
        body.put("jamId", jamId);
        body.put("jamStatus", to.name());
        return ResponseEntity.ok(body);
    }

    @PostMapping("/admin/jams/{jamId}/visibility")
    @Transactional
    public ResponseEntity<Map<String, Object>> toggleVisibility(
            @PathVariable("jamId") long jamId,
            HttpServletRequest request,
            HttpSession session) {
        ResponseEntity<Map<String, Object>> denied = requireJamManage(session);
        if (denied != null) {
            return denied;
        }
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "게임잼을 찾을 수 없습니다.");
        }

        boolean next = !Boolean.TRUE.equals(jam.getIsVisible());
        jamsMapper.updateVisibility(jamId, next);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", HttpStatus.OK.value());
        body.put("message", next ? "게임잼을 노출했습니다." : "게임잼을 숨겼습니다.");
        body.put("jamId", jamId);
        body.put("visible", next);
        return ResponseEntity.ok(body);
    }

    @PostMapping("/admin/jams/{jamId}/delete")
    @Transactional
    public ResponseEntity<Map<String, Object>> delete(
            @PathVariable("jamId") long jamId,
            HttpServletRequest request,
            HttpSession session) {
        ResponseEntity<Map<String, Object>> denied = requireJamManage(session);
        if (denied != null) {
            return denied;
        }
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        JamData jam = jamsMapper.getById(jamId);
        if (jam == null) {
            return response(HttpStatus.NOT_FOUND, "게임잼을 찾을 수 없습니다.");
        }

        jamsMapper.softDelete(jamId);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", HttpStatus.OK.value());
        body.put("message", "게임잼을 삭제했습니다.");
        body.put("jamId", jamId);
        return ResponseEntity.ok(body);
    }

    private ResponseEntity<Map<String, Object>> requireJamManage(HttpSession session) {
        if (!gate.isAuthenticated(session)) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }
        if (!gate.has(session, PermissionKeys.GAME_JAM_MANAGE.name())) {
            return response(HttpStatus.FORBIDDEN, "권한이 없습니다.");
        }
        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);
    }

    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 String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String text = value.trim();
        return text.isBlank() ? null : text;
    }

    private OffsetDateTime parseOffset(String raw) {
        String text = trimToNull(raw);
        if (text == null) {
            return null;
        }
        return OffsetDateTime.parse(text);
    }
}
