package com.alterdekim.game.service;

import com.alterdekim.game.component.game.*;
import com.alterdekim.game.component.game.avatar.*;
import com.alterdekim.game.component.game.friends.UserFriend;
import com.alterdekim.game.component.game.inventory.Inventory;
import com.alterdekim.game.component.game.inventory.InventoryList;
import com.alterdekim.game.component.game.inventory.InventoryTab;
import com.alterdekim.game.component.game.response.init.UserAccount;
import com.alterdekim.game.component.game.response.init.UserInfo;
import com.alterdekim.game.component.game.response.init.UserInitInfo;
import com.alterdekim.game.component.game.response.init.UserPhone;
import com.alterdekim.game.entity.*;
import com.alterdekim.game.repository.RoleRepository;
import com.alterdekim.game.repository.UserPropertyRepository;
import com.alterdekim.game.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserPropertyRepository propertyRepository;

    @Autowired
    private OnlineStatusService onlineStatusService;

    @Autowired
    private AvatarInventoryService inventoryService;

    @Autowired
    private PhoneIconService phoneIconService;

    @Autowired
    private RelationshipService relationshipService;

    @Autowired
    private IncompatibleService incompatibleService;


    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public List<User> getAll() {
        return this.userRepository.findAll();
    }

    public int saveUser(String username, String password, Role.RoleType role) {
        User user = new User();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(password));
        Role r = roleRepository.findByName(role);
        if(r == null) {
            r = checkRoleExist(role);
        }
        user.setRoles(Collections.singletonList(r));
        return userRepository.save(user).getId();
    }

    private Role checkRoleExist(Role.RoleType role) {
        return roleRepository.save(new Role(role));
    }

    public Optional<Object> findUserProperty(Integer userId, PlayerProperties type) {
        Optional<UserProperty> res = this.propertyRepository.findByUserIdAndTag(userId, type);
        if( res.isEmpty() ) return Optional.empty();
        UserProperty property = res.get();
        return Optional.of(switch (property.getType().getValueType()) {
            case String -> property.getStringVal();
            case Integer -> property.getIntegerVal();
            case Boolean -> property.getBooleanVal();
            case Date -> property.getDateVal();
            case Enum -> property.getType() == PlayerProperties.RoleFlags ? RoleFlags.valueOf(property.getStringVal()) : ClubAccessType.valueOf(property.getStringVal());
        });
    }

    public void removeUserProperty(Integer userId, PlayerProperties type) {
        this.propertyRepository.deleteByUserIdAndType(userId, type);
    }

    public void pushUserProperty(Integer userId, PlayerProperties type, Object val) {
        this.propertyRepository.deleteByUserIdAndType(userId, type);
        this.propertyRepository.save(switch (type.getValueType()) {
            case String -> new UserProperty(userRepository.getReferenceById(userId), (String) val, type);
            case Integer -> new UserProperty(userRepository.getReferenceById(userId), (Integer) val, type);
            case Boolean -> new UserProperty(userRepository.getReferenceById(userId), (Boolean) val, type);
            case Date -> new UserProperty(userRepository.getReferenceById(userId), (LocalDateTime) val, type);
            case Enum -> new UserProperty(userRepository.getReferenceById(userId), String.valueOf(val), type);
        });
    }

    public RoleFlags getRoleFlags(Integer userId) {
        Optional<Object> res = this.findUserProperty(userId, PlayerProperties.RoleFlags);
        if( res.isEmpty() ) {
            this.pushUserProperty(userId, PlayerProperties.RoleFlags, RoleFlags.SA);
            return RoleFlags.SA;
        }
        return (RoleFlags) res.get();
    }

    public Boolean getIsClubPresent(Integer userId) {
        // todo: Implement clubs;
        return false;
    }

    public ClubAccessType getClubAccessType(Integer userId) {
        Optional<Object> res = this.findUserProperty(userId, PlayerProperties.ClubAccessType);
        if( res.isEmpty() ) {
            this.pushUserProperty(userId, PlayerProperties.ClubAccessType, ClubAccessType.CLOSED);
            return ClubAccessType.CLOSED;
        }
        return (ClubAccessType) res.get();
    }

    public Boolean getIsFriend(Integer userId, Integer userId2) {
        return relationshipService.isFriends(userId, userId2);
    }

    public void setWeaponsCount(Integer userId, Integer count) {
        this.pushUserProperty(userId, PlayerProperties.WeaponsCount, count);
    }

    public Integer getUsualTickets(Integer userId) {
        return this.getIntegerUserProperty(userId, PlayerProperties.UsualTickets, 1000);
    }

    public void setUsualTickets(Integer userId, Integer amount) {
        this.pushUserProperty(userId, PlayerProperties.UsualTickets, amount);
    }

    public Integer getMagicTickets(Integer userId) {
        return getIntegerUserProperty(userId, PlayerProperties.MagicTickets, 200);
    }

    public void setMagicTickets(Integer userId, Integer amount) {
        this.pushUserProperty(userId, PlayerProperties.MagicTickets, amount);
    }
    
    public Integer getIntegerUserProperty(Integer userId, PlayerProperties prop, Integer def) {
        Optional<Object> res = this.findUserProperty(userId, prop);
        if( res.isEmpty() ) {
            this.pushUserProperty(userId, prop, def);
            return def;
        }
        return res.map(o -> (Integer) o).get();
    }

    public Boolean getBooleanUserProperty(Integer userId, PlayerProperties prop, Boolean def) {
        Optional<Object> res = this.findUserProperty(userId, prop);
        if( res.isEmpty() ) {
            this.pushUserProperty(userId, prop, def);
            return def;
        }
        return res.map(o -> (Boolean) o).get();
    }

    public Optional<String> getUsernameById(Integer userId) {
        return this.userRepository.findById(userId).map(User::getUsername);
    }

    public UserAccount getUserAccountByUserId(Integer userId) {
        return new UserAccount(
                getIntegerUserProperty(userId, PlayerProperties.PhoneCardBalance, 10),
                getIntegerUserProperty(userId, PlayerProperties.WeaponsCount, 15),
                getIntegerUserProperty(userId, PlayerProperties.OxygenUnits, 99999)
        );
    }

    public void setDefaultPhone(Integer userId, Long phoneId) {
        this.setDefault(userId, phoneId, AvatarInventoryType.Phones);
    }

    public void setDefaultBackground(Integer userId, Long bgId) {
        this.setDefault(userId, bgId, AvatarInventoryType.Backgrounds);
    }

    private void setDefault(Integer userId, Long gid, AvatarInventoryType type) {
        this.inventoryService.setDefault(userId, gid, type);
    }

    public void setDefaultInventoryItem(Integer userId, Long gid, AvatarInventoryType type, Integer color) {
        if( !this.inventoryService.isSpecificItemExists(userId, type, gid) ) {
            this.inventoryService.addGoodToInventory(new AvatarInventory(userRepository.getReferenceById(userId), gid, true, type, color));
        } else {
            this.inventoryService.setUsed(userId, gid, type);
            this.inventoryService.setColor(userId, gid, type, color);
        }
    }

    public void resetUsedClothes(Integer userId) {
        this.inventoryService.resetUsed(userId, AvatarInventoryType.BodyParts);
        this.inventoryService.resetUsed(userId, AvatarInventoryType.Clothes_);
        this.inventoryService.resetUsed(userId, AvatarInventoryType.Caps);
        this.inventoryService.resetUsed(userId, AvatarInventoryType.Glasses);
        this.inventoryService.resetUsed(userId, AvatarInventoryType.Suits);
        this.inventoryService.resetUsed(userId, AvatarInventoryType.Bangles);
        this.inventoryService.resetUsed(userId, AvatarInventoryType.Boots);
    }

    public UserPhone getUserPhoneByUserId(Integer userId) {
        Long phoneId = inventoryService.getSelectedPhoneId(userId);
        return new UserPhone(
                999,
                phoneIconService.findSkinById(phoneId).map(PhoneSkin::getSkinMRId).orElse(401L),
                phoneId.intValue(),
                getIntegerUserProperty(userId, PlayerProperties.PhoneCardBalance, 10)
        );
    }

    public Inventory getUserAvatarByUserId(Integer userId) {
        return new Inventory(
            new InventoryTab(199, List.of(
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Body, false),
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Legs, false)
            )),
            new InventoryTab(205, List.of(
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Horn, true),
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Beak, true),
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Mouth, true),
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Nose, true),
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Eyes, false),
                    inventoryService.getBodyPartInventoryGroup(userId, BodyPartType.Ears, true)
            )),
            new InventoryTab(206, List.of(
                    inventoryService.getClothInventoryGroup(userId, GoodClothType.Clothes),
                    inventoryService.getClothInventoryGroup(userId, GoodClothType.Caps),
                    inventoryService.getClothInventoryGroup(userId, GoodClothType.Glasses),
                    inventoryService.getClothInventoryGroup(userId, GoodClothType.Boots),
                    inventoryService.getClothInventoryGroup(userId, GoodClothType.Bangles),
                    inventoryService.getClothInventoryGroup(userId, GoodClothType.Suits)
            )),
            new InventoryList(230, inventoryService.getBackgroundsByUserId(userId)),
            new InventoryList(231, inventoryService.getPhonesByUserId(userId)),
            new InventoryList(211, Collections.emptyList()),
            incompatibleService.getAll()
        );
    }

    public UserInfo getUserInfoByUserId(Integer userId, boolean isForUserInit, Integer plId) {
        // todo: implement hardcoded stuff
        return new UserInfo(
                userId,
                !isForUserInit ? userId : null,
                getRoleFlags(userId),
                !isForUserInit ? true : null,
                getBooleanUserProperty(userId, PlayerProperties.IsLimited, false),
                onlineStatusService.isOnline(userId),
                getBooleanUserProperty(userId, PlayerProperties.IsMinorSecretAgent, false),
                getBooleanUserProperty(userId, PlayerProperties.IsHomeLocked, true),
                getBooleanUserProperty(userId, PlayerProperties.IsClubLocked, true),
                getIsClubPresent(userId),
                getClubAccessType(userId),
                null,
                getIsFriend(userId, plId),
                getIntegerUserProperty(userId, PlayerProperties.Experience, 0),
                getIntegerUserProperty(userId, PlayerProperties.Level, 80),
                getIntegerUserProperty(userId, PlayerProperties.MagicLevel, 1),
                getIntegerUserProperty(userId, PlayerProperties.RaceLevel, 1),
                inventoryService.getSelectedPhoneId(userId).intValue(),
                inventoryService.getUsedBackground(userId),
                getIntegerUserProperty(userId, PlayerProperties.CampId, 1),
                getIntegerUserProperty(userId, PlayerProperties.NiftTotalCount, 1),
                "Server",
                LocalDateTime.now(),
                getIntegerUserProperty(userId, PlayerProperties.InstructorLevel, 1),
                getUsualTickets(userId),
                getMagicTickets(userId),
                getIntegerUserProperty(userId, PlayerProperties.VisaId, 10),
                getIntegerUserProperty(userId, PlayerProperties.CurrentUseCount, 10),
                getIntegerUserProperty(userId, PlayerProperties.LastNewspaperId, 10),
                getBooleanUserProperty(userId, PlayerProperties.HasSnFriends, true),
                getIntegerUserProperty(userId, PlayerProperties.MinsLeft, 10),
                getIntegerUserProperty(userId, PlayerProperties.TimesUsed, 10),
                this.getUsernameById(userId).get(),
                inventoryService.getAchievementsByUserId(userId),
                isForUserInit ? null : inventoryService.getMagicAbilitiesItemsByUserId(userId)
        );
    }

    public List<IBodyAvatarItem> getOtherUserAvatar(Integer userId) {
        return this.inventoryService.getWholeAvatarByUserId(userId);
    }

    public UserInitInfo getUserInitInfoByUserId(Integer userId) {
        return new UserInitInfo(
                getUserAccountByUserId(userId),
                getUserInfoByUserId(userId, true, userId),
                getUserPhoneByUserId(userId),
                inventoryService.getMagicAbilitiesItemsByUserId(userId),
                inventoryService.getSmilesByUserId(userId)
        );
    }

    public PlayerAvatar getAvatarById(int userId, Player p) {
        Map<String, BodyAvatarItem> body = new HashMap<>();
        List<BodyAvatarItem> i = inventoryService.getWholeAvatarByUserId(userId).stream().map(BodyAvatarItem::fromInterface).collect(Collectors.toList());
        IntStream.range(0, i.size()).boxed().forEach(g -> body.put(String.valueOf(g+1), i.get(g)));
        UserAvatar a = new UserAvatar(getIntegerUserProperty(userId, PlayerProperties.Level, 80), getUsernameById(userId).get(), p.getX(), p.getY(), p.getState(), body);
        return new PlayerAvatar(String.valueOf(getRoleFlags(userId).getValue()), a);
    }

    public void removeUser(int userId) {
        this.userRepository.deleteById(userId);
    }

    public Long getUsersCount() {
        return this.userRepository.findUsersCount();
    }

    public Map<String, UserFriend> getFriendsOfUser(Integer userId, Map<Integer, Player> players) {
        return this.convertUsersToFriends(relationshipService.getFriendsOfUser(userId), players);
    }

    public Map<String, UserFriend> getRequestsOfUser(Integer userId, Map<Integer, Player> players) {
        return this.convertUsersToFriends(relationshipService.getRequestsOfUser(userId), players);
    }

    private Map<String, UserFriend> convertUsersToFriends(List<User> l, Map<Integer, Player> players) {
        Map<String, UserFriend> m = new HashMap<>();
        for( int i = 0, u = 1; i < l.size(); i++, u++ ) {
            User user = l.get(i);
            m.put(String.valueOf(u), new UserFriend(user.getId(), "", user.getUsername(), players.containsKey(user.getId())));
        }
        return m;
    }

    public void removeFriendshipWithUser(Integer userId, Integer friendId) {
        relationshipService.deleteFriendshipByUserIds(userId, friendId);
    }

    public void acceptFriendshipWithUsers(Integer userId, List<Integer> users) {
        relationshipService.acceptFriendshipWithUsers(userId, users);
    }

    public void denyFriendshipsWithUsers(Integer userId, List<Integer> users) {
        relationshipService.denyFriendshipsWithUsers(userId, users);
    }

    public void pushToInventory(int userId, AvatarInventoryType type, long goodId) {
        if( !this.inventoryService.isSpecificItemExists(userId, type, goodId) ) {
            this.inventoryService.addGoodToInventory(new AvatarInventory(userRepository.getReferenceById(userId), goodId, false, type));
        }
    }
}
