package com.alterdekim.game.controller;

import com.alterdekim.game.component.GameServer;
import com.alterdekim.game.component.NativeDao;
import com.alterdekim.game.component.StartUpListener;
import com.alterdekim.game.component.game.ClubAccessType;
import com.alterdekim.game.component.game.PlayerProperties;
import com.alterdekim.game.component.game.PlayerPropertyType;
import com.alterdekim.game.component.game.RoleFlags;
import com.alterdekim.game.component.game.avatar.GoodClothType;
import com.alterdekim.game.component.game.inventory.InventoryItem;
import com.alterdekim.game.controller.result.api.ApiResult;
import com.alterdekim.game.controller.result.api.PreloaderResult;
import com.alterdekim.game.controller.result.api.PromotionBannerResult;
import com.alterdekim.game.controller.result.api.PromotionResult;
import com.alterdekim.game.entity.*;
import com.alterdekim.game.repository.AvatarInventoryRepository;
import com.alterdekim.game.service.*;
import com.alterdekim.game.utils.DateUtils;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/api")
public class ApiController {
    @Autowired
    private Environment env;

    @Autowired
    private NativeDao nativeDao;

    @Autowired
    private StartUpListener startUpListener;

    @Autowired
    private GameServer gameServer;

    @Autowired
    private UserService userService;

    @Autowired
    private PreloaderService preloaderService;

    @Autowired
    private PromotionService promotionService;

    @Autowired
    private PromotionBannerService promotionBannerService;

    @Autowired
    private LocationService locationService;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private MRService mrService;

    @Autowired
    private MRTService mrtService;

    @Autowired
    private TRService trService;

    @Autowired
    private AvatarInventoryRepository avatarInventoryRepository;

    @PostMapping("/delete_row")
    public ResponseEntity deleteRow(@RequestParam("table") APITable table, @RequestParam("row") Long rowId) {
        switch (table) {
            case Preloaders -> preloaderService.removeById(rowId);
            case Promotions -> promotionService.removeById(rowId);
            case Banners -> promotionBannerService.removeById(rowId);
            case Locations -> locationService.removeById(rowId);
            case LocationObjects -> locationService.removeObjectById(rowId);
        }
        return ResponseEntity.ok().build();
    }

    @PostMapping("/update_table")
    public ResponseEntity updateRow(@RequestParam("table") APITable table,
                                    @RequestParam("column") String column,
                                    @RequestParam("id") Long id,
                                    @RequestParam("val") Boolean val) {
        if( column.matches("[^a-zA-Z]") || id < 1 ) return ResponseEntity.badRequest().build();
        nativeDao.updateTable(table.getVal(), column, val, id);
        return ResponseEntity.noContent().build();
    }

    @RequestMapping(value = "/get_entity_info", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, String>> getEntityInfo(@RequestParam("table") APITable table) {
        Map<String, String> m = new HashMap<>();
        for( Field f : table.getCl().getDeclaredFields() ) {
            m.put(f.getName(), f.getType().getSimpleName());
        }
        return ResponseEntity.ok(m);
    }

    @Getter
    @AllArgsConstructor
    public enum APITable {
        Preloaders("preloaders", Preloader.class, StaticController.PanelSection.Preloaders),
        Promotions("promotions", Promotion.class, StaticController.PanelSection.Preloaders),
        Banners("promotion_banners", PromotionBanner.class, StaticController.PanelSection.Preloaders),
        Locations("locations", Location.class, StaticController.PanelSection.Locations),
        LocationObjects("location_objects",LocationObjectInstance .class, StaticController.PanelSection.Locations),
        Users("users", User.class, StaticController.PanelSection.Users);

        private final String val;
        private final Class<?> cl;
        private final StaticController.PanelSection section;
    }

    @GetMapping("/get_dashboard_logs")
    public ResponseEntity<String> getLogs() {
        try {
            Path logFile = Paths.get(env.getProperty("logging.file.name"));
            RandomAccessFile file = new RandomAccessFile(logFile.toFile(), "r");
            int n = 2048;
            file.seek(logFile.toFile().length() - n);
            byte[] b = new byte[n];
            file.read(b, 0, n);
            return ResponseEntity.ok(new String(b));
        } catch (IOException e) {
            log.error("getLogs error: {}", e.getMessage());
            return ResponseEntity.badRequest().build();
        }
    }

    @GetMapping("/get_uptime")
    public ResponseEntity<String> getUpTime() {
        return ResponseEntity.ok(
                DateUtils.prettyPrint(Duration.of(System.currentTimeMillis() - startUpListener.getStartTime(), ChronoUnit.MILLIS))
        );
    }

    @GetMapping("/get_players_online")
    public ResponseEntity<Integer> getPlayersOnline() {
        return ResponseEntity.ok(gameServer.getPlayersCount());
    }

    @GetMapping("/get_users_count")
    public ResponseEntity<Long> getUsersCount() {
        return ResponseEntity.ok(userService.getUsersCount());
    }

    @RequestMapping(value = "/get_entity_rows", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<Object>> getEntityRows(@RequestParam("entity") APITable table) {
        var r = switch (table) {
            case Preloaders -> preloaderService.getAll();
            case Promotions -> promotionService.getAll();
            case Banners -> promotionBannerService.getAll();
            case Locations -> locationService.getAll();
            case LocationObjects -> locationService.getAllLocationObjects();
            case Users -> userService.getAll();
        };

        return ResponseEntity.ok(
                r
                .stream()
                .map(ApiResult::toAPIResult)
                .collect(Collectors.toList())
        );
    }

    @RequestMapping(value = "/reset_user_property", method = RequestMethod.POST)
    public ResponseEntity resetUserProperty(@RequestParam("user_id") Integer userId, @RequestParam("entry_name") String entryName) {
        PlayerProperties property = PlayerProperties.valueOf(entryName);
        userService.removeUserProperty(userId, property);
        userService.getUserInitInfoByUserId(userId);
        return ResponseEntity.ok().build();
    }

    @RequestMapping(value = "/edit_user_property", method = RequestMethod.POST)
    public ResponseEntity editUserProperty(@RequestParam("user_id") Integer userId,
                                           @RequestParam("entry_name") String entryName,
                                           @RequestParam("entry_value") String entryValue) {
        PlayerProperties property = PlayerProperties.valueOf(entryName);
        Object val = switch (property.getValueType()) {
            case Enum -> {
                if( property == PlayerProperties.RoleFlags ) {
                    yield RoleFlags.valueOf(entryValue);
                }
                yield ClubAccessType.valueOf(entryValue);
            }
            case Boolean -> Boolean.parseBoolean(entryValue);
            default -> Integer.parseInt(entryValue);
        };
        userService.pushUserProperty(userId, property, val);
        return ResponseEntity.ok().build();
    }

    @RequestMapping(value = "/get_user_properties", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<UserPropertyResult>> getUserProperties(@RequestParam("id") Integer userId) {
        List<UserPropertyResult> results = new LinkedList<>();
        for( PlayerProperties property : PlayerProperties.values() ) {
            Object value = userService.findUserProperty(userId, property).orElse(null);
            if( value == null ) continue;
            results.add(new UserPropertyResult(property.getValueType(), property.name(), value));
        }
        return ResponseEntity.ok(results);
    }

    @RequestMapping(value = "/get_goods", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<GoodResult>> getGoods() {
        List<GoodResult> results = new LinkedList<>();
        for( Good good : goodsService.getAll() ) {
            GoodResult result = new GoodResult();
            long goodId = good.getId();
            result.setGoodId(goodId);
            Integer iconId = -good.getMRId();
            Integer trId = good.getTRId();
            var icon = mrService.findById(iconId.longValue());
            if( icon.isPresent() ) {
                String url = mrtService.findById(icon.get().getTId().longValue()).getVal() + icon.get().getUrl();
                result.setUrl(url);
            }
            var name = trService.findById(trId.longValue());
            if( name.isPresent() ) {
                String text = name.get().getH();
                result.setName(text);
            }
            results.add(result);
        }
        return ResponseEntity.ok(results);
    }

    @RequestMapping(value = "/get_avatar_inventory", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<GoodResult>> getInventory(@RequestParam("id") Integer userId) {
        List<GoodResult> results = new LinkedList<>();
        List<InventoryItem> items = Arrays.stream(GoodClothType.values())
                .map(type -> this.avatarInventoryRepository.findInventoryItems(userId, type))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        for( InventoryItem avatarInventory : items) {
            var og = goodsService.findById(avatarInventory.getId());
            if( og.isPresent() ) {
                var good = og.get();
                GoodResult result = new GoodResult();
                long goodId = good.getId();
                result.setGoodId(goodId);
                Integer iconId = -good.getMRId();
                Integer trId = good.getTRId();
                var icon = mrService.findById(iconId.longValue());
                if (icon.isPresent()) {
                    String url = mrtService.findById(icon.get().getTId().longValue()).getVal() + icon.get().getUrl();
                    result.setUrl(url);
                }
                var name = trService.findById(trId.longValue());
                if (name.isPresent()) {
                    String text = name.get().getH();
                    result.setName(text);
                }
                results.add(result);
            }
        }
        return ResponseEntity.ok(results);
    }


    @Setter
    @NoArgsConstructor
    public static class GoodResult {
        @JsonProperty
        private String url;
        @JsonProperty
        private Long goodId;
        @JsonProperty
        private String name;
    }

    @AllArgsConstructor
    public static class UserPropertyResult {
        @JsonProperty
        private PlayerPropertyType type;
        @JsonProperty
        private String name;
        @JsonProperty
        private Object value;
    }

    @RequestMapping(value = "/add_row", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<AddEntryResponse> addSpecificRow(HttpServletRequest httpServletRequest) {
        if( !httpServletRequest.getParameterMap().containsKey("table") ) return ResponseEntity.badRequest().build();
        try {
            APITable table = APITable.valueOf(httpServletRequest.getParameterMap().get("table")[0]);
            List<Field> fields = Arrays.stream(table.getCl().getDeclaredFields())
                    .filter(f -> httpServletRequest.getParameterMap().containsKey(f.getName()))
                    .collect(Collectors.toList());

            Map<String, String> vals = new HashMap<>();
            for (Field f : fields) {
                String val = httpServletRequest.getParameterMap().get(f.getName())[0];
                val = switch (f.getType().getSimpleName()) {
                    case "String" -> "'" + val + "'";
                    case "Boolean" -> val.equals("true") ? "1" : "0";
                    default -> val;
                };
                vals.put(f.getName(), val);
            }
            nativeDao.insertEntity(table.getVal(), vals);
            return ResponseEntity.ok(new AddEntryResponse(table.getSection().name()));
        } catch (Exception e) {
            log.error("addEntry error: {}", e.getMessage());
        }
        return ResponseEntity.internalServerError().build();
    }

    @AllArgsConstructor
    private class AddEntryResponse {
        @JsonProperty
        private String section;
    }
}
