package com.pandoli365.bibimbap.controller.api;

import com.pandoli365.bibimbap.data.GameData;
import com.pandoli365.bibimbap.game.GameCatalog;
import com.pandoli365.bibimbap.mapper.GameCommentsMapper;
import com.pandoli365.bibimbap.mapper.GameReviewsMapper;
import com.pandoli365.bibimbap.mapper.GameViewsMapper;
import com.pandoli365.bibimbap.mapper.GamesMapper;
import com.pandoli365.bibimbap.security.CsrfTokens;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
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.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 java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@Controller
public class GameController {

    private final GamesMapper gamesMapper;
    private final GameCommentsMapper gameCommentsMapper;
    private final GameReviewsMapper gameReviewsMapper;
    private final GameViewsMapper gameViewsMapper;

    @Value("${app.webgl.asset-origin:}")
    private String webglAssetOrigin;

    public GameController(GamesMapper gamesMapper,
                         GameCommentsMapper gameCommentsMapper,
                         GameReviewsMapper gameReviewsMapper,
                         GameViewsMapper gameViewsMapper) {
        this.gamesMapper = gamesMapper;
        this.gameCommentsMapper = gameCommentsMapper;
        this.gameReviewsMapper = gameReviewsMapper;
        this.gameViewsMapper = gameViewsMapper;
    }

    public static String webglUrlForGame(int gameId) {
        return "/webgl/game-" + gameId + "/index.html";
    }

    @PostMapping("/game/new")
    @Transactional
    public ResponseEntity<Map<String, Object>> createGame(
            @RequestParam(name = "name", required = false) String name,
            @RequestParam(name = "gitUrl", required = false) String gitUrl,
            @RequestParam(name = "creatorNote", required = false) String creatorNote,
            @RequestParam(name = "webglPath", required = false) String webglPath,
            @RequestParam(name = "thumbnailUrl", required = false) String thumbnailUrl,
            @RequestParam(name = "visible", required = false) String visible,
            HttpServletRequest request,
            HttpSession session
    ) {
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        Long userId = sessionUserId(session);
        if (userId == null) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }

        String normalizedName = trimToNull(name);
        String normalizedWebglPath = trimToNull(webglPath);
        if (normalizedName == null || normalizedName.length() > 80) {
            return response(HttpStatus.BAD_REQUEST, "게임 이름을 확인해 주세요.");
        }
        if (!isGameAssetPath(normalizedWebglPath) || !normalizedWebglPath.endsWith("/index.html")) {
            return response(HttpStatus.BAD_REQUEST, "WebGL zip 업로드를 먼저 완료해 주세요.");
        }

        String normalizedThumbnailUrl = trimToNull(thumbnailUrl);
        if (normalizedThumbnailUrl != null && !isGameAssetPath(normalizedThumbnailUrl)) {
            return response(HttpStatus.BAD_REQUEST, "썸네일 경로가 올바르지 않습니다.");
        }

        String normalizedCreatorNote = trimToEmpty(creatorNote);
        if (normalizedCreatorNote.length() > 600) {
            return response(HttpStatus.BAD_REQUEST, "소개는 600자 이하로 입력해 주세요.");
        }

        GameData game = new GameData();
        game.setUserId(userId);
        game.setName(normalizedName);
        game.setCreatorNote(normalizedCreatorNote);
        game.setGitUrl(safeExternalUrl(gitUrl));
        game.setWebglPath(normalizedWebglPath);
        game.setThumbnailUrl(normalizedThumbnailUrl == null ? "" : normalizedThumbnailUrl);
        game.setVisible(isChecked(visible));
        game.setSortOrder(gamesMapper.nextSortOrder());

        gamesMapper.addGame(game);
        if (game.getId() == null) {
            return response(HttpStatus.INTERNAL_SERVER_ERROR, "게임 등록 결과를 확인하지 못했습니다.");
        }

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", 200);
        body.put("message", "게임 등록이 완료되었습니다.");
        body.put("gameId", game.getId());
        body.put("location", "/game/" + game.getId());
        return ResponseEntity.ok(body);
    }

    @GetMapping("/game/{id}")
    @Transactional
    public String gameDetail(@PathVariable("id") long id, Model model, HttpSession session) {
        GameData game = gamesMapper.getGame(id);
        if (game != null) {
            recordView(id, session);
            addGameModel(model, game, sessionUserId(session));
            model.addAttribute("comments", gameCommentsMapper.listGameComments(id, "oldest", 0, 20));
            model.addAttribute("reviews", gameReviewsMapper.listGameReviews(id, "newest", 0, 20));
            model.addAttribute("userRole", (String) session.getAttribute("role"));
            return "game-detail";
        }

        if (id < Integer.MIN_VALUE || id > Integer.MAX_VALUE || !GameCatalog.isValidId((int) id)) {
            return "redirect:/";
        }

        int intId = (int) id;
        int idx = GameCatalog.toIndex(intId);
        model.addAttribute("gameId", intId);
        model.addAttribute("gameName", GameCatalog.NAMES[idx]);
        model.addAttribute("creator", GameCatalog.CREATORS[idx]);
        model.addAttribute("likeCount", GameCatalog.LIKE_COUNTS[idx]);
        model.addAttribute("likeCountFormatted", String.format("%,d", GameCatalog.LIKE_COUNTS[idx]));
        model.addAttribute("creatorNote", GameCatalog.CREATOR_NOTES[idx]);
        model.addAttribute("gitUrl", safeExternalUrl(GameCatalog.GIT_URLS[idx]));
        model.addAttribute("webglUrl", webglUrlForGame(intId));
        model.addAttribute("webglFrameSrc", webglFrameSrc(webglUrlForGame(intId)));
        model.addAttribute("webglDeployPath", webglUrlForGame(intId));
        model.addAttribute("owner", false);
        model.addAttribute("comments", List.of());
        model.addAttribute("reviews", List.of());
        model.addAttribute("currentUserId", sessionUserId(session));
        model.addAttribute("userRole", (String) session.getAttribute("role"));
        return "game-detail";
    }

    @GetMapping("/game/{id}/edit")
    public String gameEditView(@PathVariable("id") long id, Model model, HttpSession session) {
        Long userId = sessionUserId(session);
        if (userId == null) {
            return "redirect:/login";
        }

        GameData game = gamesMapper.getGame(id);
        if (game == null) {
            return "redirect:/";
        }
        if (!userId.equals(game.getUserId())) {
            return "redirect:/game/" + id;
        }

        model.addAttribute("editMode", true);
        model.addAttribute("editGameId", game.getId());
        model.addAttribute("editGameName", trimToEmpty(game.getName()));
        model.addAttribute("editCreatorNote", trimToEmpty(game.getCreatorNote()));
        model.addAttribute("editGitUrl", trimToEmpty(game.getGitUrl()));
        model.addAttribute("editWebglPath", trimToEmpty(game.getWebglPath()));
        model.addAttribute("editThumbnailUrl", trimToEmpty(game.getThumbnailUrl()));
        model.addAttribute("editVisible", game.getVisible() == null || game.getVisible());
        model.addAttribute("editGameUuid", gameUuidFromPath(trimToEmpty(game.getWebglPath())));
        return "game-register";
    }

    @PostMapping("/game/{id}/edit")
    @Transactional
    public ResponseEntity<Map<String, Object>> updateGame(
            @PathVariable("id") long id,
            @RequestParam(name = "name", required = false) String name,
            @RequestParam(name = "gitUrl", required = false) String gitUrl,
            @RequestParam(name = "creatorNote", required = false) String creatorNote,
            @RequestParam(name = "webglPath", required = false) String webglPath,
            @RequestParam(name = "thumbnailUrl", required = false) String thumbnailUrl,
            @RequestParam(name = "visible", required = false) String visible,
            HttpServletRequest request,
            HttpSession session
    ) {
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        Long userId = sessionUserId(session);
        if (userId == null) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }

        GameData existing = gamesMapper.getGame(id);
        if (existing == null) {
            return response(HttpStatus.NOT_FOUND, "게임을 찾을 수 없습니다.");
        }
        if (!userId.equals(existing.getUserId())) {
            return response(HttpStatus.FORBIDDEN, "작성자만 수정할 수 있습니다.");
        }

        String normalizedName = trimToNull(name);
        String normalizedWebglPath = trimToNull(webglPath);
        if (normalizedName == null || normalizedName.length() > 80) {
            return response(HttpStatus.BAD_REQUEST, "게임 이름을 확인해 주세요.");
        }
        if (!isGameAssetPath(normalizedWebglPath) || !normalizedWebglPath.endsWith("/index.html")) {
            return response(HttpStatus.BAD_REQUEST, "WebGL zip 업로드를 먼저 완료해 주세요.");
        }

        String normalizedThumbnailUrl = trimToNull(thumbnailUrl);
        if (normalizedThumbnailUrl != null && !isGameAssetPath(normalizedThumbnailUrl)) {
            return response(HttpStatus.BAD_REQUEST, "썸네일 경로가 올바르지 않습니다.");
        }

        String normalizedCreatorNote = trimToEmpty(creatorNote);
        if (normalizedCreatorNote.length() > 600) {
            return response(HttpStatus.BAD_REQUEST, "소개는 600자 이하로 입력해 주세요.");
        }

        existing.setName(normalizedName);
        existing.setCreatorNote(normalizedCreatorNote);
        existing.setGitUrl(safeExternalUrl(gitUrl));
        existing.setWebglPath(normalizedWebglPath);
        existing.setThumbnailUrl(normalizedThumbnailUrl == null ? "" : normalizedThumbnailUrl);
        existing.setVisible(isChecked(visible));
        gamesMapper.updateGame(existing);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", 200);
        body.put("message", "게임이 수정되었습니다.");
        body.put("gameId", existing.getId());
        body.put("location", "/game/" + existing.getId());
        return ResponseEntity.ok(body);
    }

    @DeleteMapping("/game/{id}")
    @Transactional
    public ResponseEntity<Map<String, Object>> deleteGame(@PathVariable("id") long id,
                                                          HttpServletRequest request,
                                                          HttpSession session) {
        if (!CsrfTokens.isValid(request)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CsrfTokens.errorBody());
        }
        Long userId = sessionUserId(session);
        if (userId == null) {
            return response(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
        }

        GameData existing = gamesMapper.getGame(id);
        if (existing == null) {
            return response(HttpStatus.NOT_FOUND, "게임을 찾을 수 없습니다.");
        }
        if (!userId.equals(existing.getUserId())) {
            return response(HttpStatus.FORBIDDEN, "작성자만 삭제할 수 있습니다.");
        }

        gamesMapper.softDeleteGameComments(id);
        gamesMapper.softDeleteGameReviews(id);
        gamesMapper.deleteGameLikes(id);
        gamesMapper.softDeleteGame(id);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", 200);
        body.put("message", "게임이 삭제되었습니다.");
        body.put("location", "/");
        return ResponseEntity.ok(body);
    }

    private void addGameModel(Model model, GameData game, Long currentUserId) {
        int likeCount = game.getLikeCount() == null ? 0 : game.getLikeCount();
        String webglPath = trimToEmpty(game.getWebglPath());
        model.addAttribute("gameId", game.getId());
        model.addAttribute("gameName", game.getName());
        model.addAttribute("creator", game.getCreator());
        model.addAttribute("likeCount", likeCount);
        model.addAttribute("likeCountFormatted", String.format("%,d", likeCount));
        model.addAttribute("creatorNote", trimToEmpty(game.getCreatorNote()));
        model.addAttribute("gitUrl", safeExternalUrl(game.getGitUrl()));
        model.addAttribute("webglUrl", webglPath);
        model.addAttribute("webglFrameSrc", webglFrameSrc(webglPath));
        model.addAttribute("webglDeployPath", webglPath);
        model.addAttribute("owner", currentUserId != null && currentUserId.equals(game.getUserId()));
        model.addAttribute("currentUserId", currentUserId);
    }

    private String webglFrameSrc(String path) {
        String normalizedPath = trimToEmpty(path);
        String origin = trimToEmpty(webglAssetOrigin);
        if (origin.isBlank()) {
            return normalizedPath;
        }
        while (origin.endsWith("/")) {
            origin = origin.substring(0, origin.length() - 1);
        }
        return origin + (normalizedPath.startsWith("/") ? normalizedPath : "/" + normalizedPath);
    }

    private String safeExternalUrl(String value) {
        String text = trimToNull(value);
        if (text == null) {
            return "";
        }
        String lower = text.toLowerCase(Locale.ROOT);
        return lower.startsWith("http://") || lower.startsWith("https://") ? text : "";
    }

    private void recordView(long gameId, HttpSession session) {
        String vKey = viewerKey(session);
        if (!gameViewsMapper.existsRecentView(gameId, vKey)) {
            gameViewsMapper.insertView(gameId, vKey);
            gameViewsMapper.incrementViewCount(gameId);
        }
    }

    private String viewerKey(HttpSession session) {
        Long userId = sessionUserId(session);
        if (userId != null) {
            return "u:" + userId;
        }
        return "s:" + session.getId();
    }

    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 boolean isChecked(String value) {
        return "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value);
    }

    private boolean isGameAssetPath(String path) {
        return path != null && path.startsWith("/game/") && !path.contains("..");
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String text = value.trim();
        return text.isBlank() ? null : text;
    }

    private String trimToEmpty(String value) {
        String text = trimToNull(value);
        return text == null ? "" : text;
    }

    private String gameUuidFromPath(String webglPath) {
        if (!isGameAssetPath(webglPath)) {
            return "";
        }
        String[] parts = webglPath.split("/");
        return parts.length > 2 ? parts[2] : "";
    }

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