package server.logic.ws_protocol.JSON.handlers.connections;

import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetUserConnectionsGraph_Request;
import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_GetUserConnectionsGraph_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;
import shine.db.MsgSubType;
import shine.db.dao.ConnectionsStateDAO;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

public class Net_GetUserConnectionsGraph_Handler implements JsonMessageHandler {
    private static final Pattern AR_TX_ID_PATTERN = Pattern.compile("^[A-Za-z0-9_-]{43}$");
    private static final Pattern AVATAR_AR_TOKEN_PATTERN = Pattern.compile("(?:^|,)\\s*AR:([A-Za-z0-9_-]{43})\\s*(?:,|$)");

    @Override
    public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
        Net_GetUserConnectionsGraph_Request req = (Net_GetUserConnectionsGraph_Request) baseRequest;
        String requestedLogin = (req.getLogin() == null || req.getLogin().isBlank()) ? "" : req.getLogin().trim();
        if (requestedLogin.isEmpty()) {
            if (ctx != null && ctx.isAuthenticatedUser()) {
                requestedLogin = ctx.getLogin();
            } else {
                return NetExceptionResponseFactory.error(req, 422, "LOGIN_REQUIRED", "Нужно передать login пользователя");
            }
        }

        try (Connection c = shine.db.SqliteDbController.getInstance().getConnection()) {
            String canonicalLogin = findCanonicalLogin(c, requestedLogin);
            if (canonicalLogin == null) {
                return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден");
            }

            List<String> outFriends = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CLOSE_FRIEND);
            List<String> inFriends = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CLOSE_FRIEND);
            List<String> outContacts = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT);
            List<String> inContacts = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CONTACT);
            List<String> outFollows = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW);
            List<String> inFollows = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_FOLLOW);
            List<String> outSpouses = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SPOUSE);
            List<String> inSpouses = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SPOUSE);
            List<String> outParents = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_PARENT);
            List<String> inParents = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_PARENT);
            List<String> outChildren = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CHILD);
            List<String> inChildren = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_CHILD);
            List<String> outSiblings = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING);
            List<String> inSiblings = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SIBLING);
            List<String> outKnownPersons = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_KNOWN_PERSON);
            List<String> inKnownPersons = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_KNOWN_PERSON);
            List<String> outShineConfirmed = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED);
            List<String> inShineConfirmed = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_CONFIRMED);
            List<String> outShineSeen = ConnectionsStateDAO.getInstance().listOutgoingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_SEEN);
            List<String> inShineSeen = ConnectionsStateDAO.getInstance().listIncomingByRelTypeCanonical(c, canonicalLogin, MsgSubType.CONNECTION_SHINE_SEEN);

            LinkedHashSet<String> allLogins = new LinkedHashSet<>();
            allLogins.add(canonicalLogin);
            addAllLogins(allLogins, outFriends, inFriends, outContacts, inContacts, outFollows, inFollows,
                    outSpouses, inSpouses, outParents, inParents, outChildren, inChildren, outSiblings, inSiblings,
                    outKnownPersons, inKnownPersons, outShineConfirmed, inShineConfirmed, outShineSeen, inShineSeen);

            Map<String, UserMeta> metaByLogin = loadUserMeta(c, allLogins);
            List<String> spouseLogins = mergeUnique(outSpouses, inSpouses);
            List<String> parentLogins = mergeUnique(outParents, inChildren);
            List<String> childLogins = mergeUnique(outChildren, inParents);
            List<String> siblingLogins = mergeUnique(outSiblings, inSiblings);

            Net_GetUserConnectionsGraph_Response resp = new Net_GetUserConnectionsGraph_Response();
            resp.setOp(req.getOp());
            resp.setRequestId(req.getRequestId());
            resp.setStatus(WireCodes.Status.OK);
            resp.setLogin(canonicalLogin);
            resp.setOutFriends(outFriends);
            resp.setInFriends(inFriends);
            resp.setOutContacts(outContacts);
            resp.setInContacts(inContacts);
            resp.setOutFollows(outFollows);
            resp.setInFollows(inFollows);
            resp.setOutSpouses(outSpouses);
            resp.setInSpouses(inSpouses);
            resp.setOutParents(outParents);
            resp.setInParents(inParents);
            resp.setOutChildren(outChildren);
            resp.setInChildren(inChildren);
            resp.setOutSiblings(outSiblings);
            resp.setInSiblings(inSiblings);
            resp.setOutKnownPersons(outKnownPersons);
            resp.setInKnownPersons(inKnownPersons);
            resp.setOutShineConfirmed(outShineConfirmed);
            resp.setInShineConfirmed(inShineConfirmed);
            resp.setOutShineSeen(outShineSeen);
            resp.setInShineSeen(inShineSeen);
            resp.setParents(toRelativeItems(parentLogins, metaByLogin));
            resp.setChildren(toRelativeItems(childLogins, metaByLogin));
            resp.setSiblings(toRelativeItems(siblingLogins, metaByLogin));
            resp.setSpouses(toRelativeItems(spouseLogins, metaByLogin));
            resp.setAllUsers(toUserMarkItems(allLogins, metaByLogin));
            return resp;
        }
    }

    private String findCanonicalLogin(Connection c, String loginAnyCase) throws Exception {
        String sql = "SELECT login FROM solana_users WHERE login = ? COLLATE NOCASE LIMIT 1";
        try (PreparedStatement ps = c.prepareStatement(sql)) {
            ps.setString(1, loginAnyCase);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.next() ? rs.getString("login") : null;
            }
        }
    }

    @SafeVarargs
    private final void addAllLogins(Set<String> target, List<String>... lists) {
        if (target == null || lists == null) return;
        for (List<String> list : lists) {
            if (list == null) continue;
            for (String login : list) {
                String clean = (login == null) ? "" : login.trim();
                if (!clean.isEmpty()) target.add(clean);
            }
        }
    }

    private List<String> mergeUnique(List<String> first, List<String> second) {
        LinkedHashMap<String, String> byKey = new LinkedHashMap<>();
        addToMergeMap(byKey, first);
        addToMergeMap(byKey, second);
        return new ArrayList<>(byKey.values());
    }

    private void addToMergeMap(Map<String, String> target, List<String> source) {
        if (target == null || source == null) return;
        for (String login : source) {
            String clean = (login == null) ? "" : login.trim();
            if (clean.isEmpty()) continue;
            String key = normKey(clean);
            if (!target.containsKey(key)) target.put(key, clean);
        }
    }

    private String normKey(String login) {
        return String.valueOf(login == null ? "" : login).trim().toLowerCase();
    }

    private Map<String, UserMeta> loadUserMeta(Connection c, Set<String> logins) throws Exception {
        Map<String, UserMeta> out = new HashMap<>();
        if (logins == null || logins.isEmpty()) return out;

        String[] placeholders = new String[logins.size()];
        for (int i = 0; i < placeholders.length; i += 1) placeholders[i] = "?";
        String sql = """
            SELECT
                su.login AS login,
                MAX(CASE WHEN up.param = 'gender' THEN up.value END) AS gender_value,
                MAX(CASE WHEN up.param = 'official' THEN up.value END) AS official_value,
                MAX(CASE WHEN up.param = 'shine' THEN up.value END) AS shine_value,
                MAX(CASE WHEN up.param = 'ava' THEN up.value END) AS avatar_value
            FROM solana_users su
            LEFT JOIN users_params up
              ON up.login = su.login COLLATE NOCASE
             AND up.param IN ('gender', 'official', 'shine', 'ava')
            WHERE su.login COLLATE NOCASE IN (%s)
            GROUP BY su.login
            ORDER BY su.login
            """.formatted(String.join(", ", placeholders));

        try (PreparedStatement ps = c.prepareStatement(sql)) {
            int i = 1;
            for (String login : logins) {
                ps.setString(i, login);
                i += 1;
            }
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    String login = rs.getString("login");
                    if (login == null || login.isBlank()) continue;
                    UserMeta meta = new UserMeta();
                    meta.gender = normalizeGender(rs.getString("gender_value"));
                    meta.official = parseToggle(rs.getString("official_value"));
                    meta.shine = parseToggle(rs.getString("shine_value"));
                    meta.avatarAr = extractArAvatarTxId(rs.getString("avatar_value"));
                    out.put(normKey(login), meta);
                }
            }
        }
        return out;
    }

    private boolean parseToggle(String rawValue) {
        String v = String.valueOf(rawValue == null ? "" : rawValue).trim().toLowerCase();
        return "1".equals(v) || "yes".equals(v) || "true".equals(v) || "on".equals(v);
    }

    private String normalizeGender(String rawValue) {
        String v = String.valueOf(rawValue == null ? "" : rawValue).trim().toLowerCase();
        if ("male".equals(v)) return "male";
        if ("female".equals(v)) return "female";
        return "unknown";
    }

    private String genderLabel(String gender) {
        if ("male".equals(gender)) return "мужчина";
        if ("female".equals(gender)) return "женщина";
        return "не указан";
    }

    private String extractArAvatarTxId(String rawValue) {
        String value = String.valueOf(rawValue == null ? "" : rawValue).trim();
        if (value.isEmpty()) return null;
        var tokenMatch = AVATAR_AR_TOKEN_PATTERN.matcher(value);
        String txId = tokenMatch.find() ? String.valueOf(tokenMatch.group(1)).trim() : "";
        // fallback для старых/кривых значений без запятых: "SHA256:...AR:<txid>"
        if (txId.isEmpty()) {
            int idx = value.indexOf("AR:");
            if (idx >= 0) {
                int start = idx + 3;
                int end = value.indexOf(',', start);
                txId = (end >= 0 ? value.substring(start, end) : value.substring(start)).trim();
            }
        }
        if (!AR_TX_ID_PATTERN.matcher(txId).matches()) return null;
        return txId;
    }

    private Net_GetUserConnectionsGraph_Response.AvatarItem makeAvatarItem(String avatarAr) {
        String txId = String.valueOf(avatarAr == null ? "" : avatarAr).trim();
        if (txId.isEmpty()) return null;
        Net_GetUserConnectionsGraph_Response.AvatarItem avatar = new Net_GetUserConnectionsGraph_Response.AvatarItem();
        avatar.setAr(txId);
        return avatar;
    }

    private List<Net_GetUserConnectionsGraph_Response.RelativeItem> toRelativeItems(
            List<String> logins,
            Map<String, UserMeta> metaByLogin
    ) {
        List<Net_GetUserConnectionsGraph_Response.RelativeItem> items = new ArrayList<>();
        if (logins == null) return items;
        for (String login : logins) {
            String clean = (login == null) ? "" : login.trim();
            if (clean.isEmpty()) continue;
            UserMeta meta = metaByLogin.get(normKey(clean));
            String gender = (meta == null) ? "unknown" : meta.gender;

            Net_GetUserConnectionsGraph_Response.RelativeItem it = new Net_GetUserConnectionsGraph_Response.RelativeItem();
            it.setLogin(clean);
            it.setGender(gender);
            it.setGenderLabel(genderLabel(gender));
            it.setAvatar(makeAvatarItem(meta == null ? null : meta.avatarAr));
            items.add(it);
        }
        return items;
    }

    private List<Net_GetUserConnectionsGraph_Response.UserMarkItem> toUserMarkItems(
            Set<String> logins,
            Map<String, UserMeta> metaByLogin
    ) {
        List<Net_GetUserConnectionsGraph_Response.UserMarkItem> items = new ArrayList<>();
        if (logins == null) return items;

        for (String login : logins) {
            String clean = (login == null) ? "" : login.trim();
            if (clean.isEmpty()) continue;

            UserMeta meta = metaByLogin.get(normKey(clean));
            boolean official = meta != null && meta.official;
            boolean shine = meta != null && meta.shine;

            Net_GetUserConnectionsGraph_Response.UserMarkItem it = new Net_GetUserConnectionsGraph_Response.UserMarkItem();
            it.setLogin(clean);
            it.setOfficial(official);
            it.setShine(shine);
            it.setOfficialLabel(official ? "официальный" : "неофициальный");
            it.setShineLabel(shine ? "сияющий" : "несияющий");
            it.setAvatar(makeAvatarItem(meta == null ? null : meta.avatarAr));
            items.add(it);
        }
        return items;
    }

    private static final class UserMeta {
        private String gender = "unknown";
        private boolean official = false;
        private boolean shine = false;
        private String avatarAr = null;
    }
}
