import time

import requests
from requests import ReadTimeout

from core.aochat import server_packets
from core.db import DB
from core.decorators import instance, timerevent
from core.dict_object import DictObject
from core.logger import Logger


@instance()
class PorkService:
    def __init__(self):
        self.logger = Logger(__name__)
        self.updates = []

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.character_service = registry.get_instance("character_service")

    def pre_start(self):
        self.bot.register_packet_handler(server_packets.CharacterLookup.id, self.update)
        self.bot.register_packet_handler(server_packets.CharacterName.id, self.update)

    def start(self):
        self.db.shared.exec(
            "CREATE TABLE IF NOT EXISTS player ("
            "char_id BIGINT PRIMARY KEY, "
            "first_name VARCHAR(30) NOT NULL, "
            "name VARCHAR(20) NOT NULL, "
            "last_name VARCHAR(30) NOT NULL, "
            "level SMALLINT NOT NULL, "
            "breed VARCHAR(20) NOT NULL, "
            "gender VARCHAR(20) NOT NULL, "
            "faction VARCHAR(20) NOT NULL, "
            "profession VARCHAR(20) NOT NULL, "
            "profession_title VARCHAR(50) NOT NULL, "
            "ai_rank VARCHAR(20) NOT NULL, "
            "ai_level SMALLINT, "
            "org_id INT DEFAULT NULL, "
            "org_name VARCHAR(255) NOT NULL, "
            "org_rank_name VARCHAR(20) NOT NULL, "
            "org_rank_id SMALLINT NOT NULL, "
            "dimension SMALLINT NOT NULL, "
            "head_id INT NOT NULL, "
            "pvp_rating SMALLINT NOT NULL, "
            "pvp_title VARCHAR(20) NOT NULL, "
            "source VARCHAR(50) NOT NULL, "
            "last_updated INT NOT NULL, "
            "invalid int DEFAULT 0, "
            "INDEX `name` (`name`) USING BTREE, "
            "INDEX `org_id` (`org_id`) USING BTREE, "
            "INDEX `org_name` (`org_name`) USING BTREE,"
            "INDEX `org_rank_name` (`org_rank_name`) USING BTREE, "
            "INDEX `org_rank_id` (`org_rank_id`) USING BTREE, "
            "INDEX `profession` (`profession`) USING BTREE, "
            "INDEX `level` (`level`) USING BTREE, "
            "INDEX `ai_level` (`ai_level`) USING BTREE)")

        self.db.create_view("player")

    # forces a lookup from remote PoRK server
    # this should not be called directly unless you are requesting info for a char on a different server
    # since cache will not be used and the result will also update the cache
    def request_char_info(self, char_name, server_num):
        url = self.get_pork_url(server_num, char_name)

        try:
            r = requests.get(url, timeout=5)
            result = r.json()
        except ReadTimeout:
            self.logger.warning("Timeout while requesting '%s'" % url)
            result = None
        except ValueError as e:
            # noinspection PyUnboundLocalVariable
            self.logger.debug("Error marshalling value as json for url '%s': %s" % (url, r.text), e)
            result = None

        char_info = None
        if result:
            char_info_json = result[0]
            org_info_json = result[1] if result[1] else {}

            char_info = DictObject({
                "name": char_info_json["NAME"],
                "char_id": char_info_json["CHAR_INSTANCE"],
                "first_name": char_info_json["FIRSTNAME"],
                "last_name": char_info_json["LASTNAME"],
                "level": char_info_json["LEVELX"],
                "breed": char_info_json["BREED"],
                "dimension": char_info_json["CHAR_DIMENSION"],
                "gender": char_info_json["SEX"],
                "faction": char_info_json["SIDE"],
                "profession": char_info_json["PROF"],
                "profession_title": char_info_json["PROFNAME"],
                "ai_rank": char_info_json["RANK_name"],
                "ai_level": char_info_json["ALIENLEVEL"],
                "pvp_rating": char_info_json["PVPRATING"],
                "pvp_title": char_info_json["PVPTITLE"] or "",
                "head_id": char_info_json["HEADID"],
                "org_id": org_info_json.get("ORG_INSTANCE", 0),
                "org_name": org_info_json.get("NAME", ""),
                "org_rank_name": org_info_json.get("RANK_TITLE", ""),
                "org_rank_id": org_info_json.get("RANK", 0),
                "source": "people.anarchy-online.com",
                "cache_age": 0
            })

        return char_info

    # standard method to get character pork data when character is on the same server
    def get_character_info(self, char, max_cache_age=86400):
        char_id = self.character_service.resolve_char_to_id(char)
        char_name = self.character_service.resolve_char_to_name(char)

        t = int(time.time())

        # if there is an entry in database and it is within the cache time, use that
        db_char_info = self.get_from_database(char_id=char_id, char_name=char_name)
        if db_char_info:
            db_char_info.cache_age = t - db_char_info.last_updated

            if db_char_info.cache_age < max_cache_age and db_char_info.source != "chat_server":
                return db_char_info

        # if we can't resolve to a char_name, we can't make a call to pork
        if not char_name:
            return db_char_info

        char_info = self.request_char_info(char_name, self.bot.dimension)

        if char_info and char_info.char_id == char_id:
            self.save_character_info(char_info)

            return char_info
        else:
            # return cached info from database, even tho it's old, and set cache_age (if it exists)
            if db_char_info:
                db_char_info.cache_age = t - db_char_info.last_updated

            return db_char_info

    # forces a skeleton object into the player table in the case that PoRK does not return any data
    # call this method if you don't need the data now but want to ensure there is a record in the database
    def load_character_info(self, char_id, char_name=None, skeleton_only=False):
        char_info = self.get_character_info(char_id)
        if not skeleton_only and (not char_info and char_name):
            char_info = self.get_character_info(char_name)
        if not char_info:
            char_info = DictObject({
                "name": "Unknown:" + str(char_id),
                "char_id": char_id,
                "first_name": "",
                "last_name": "",
                "level": 0,
                "breed": "",
                "dimension": self.bot.dimension,
                "gender": "",
                "faction": "",
                "profession": "",
                "profession_title": "",
                "ai_rank": "",
                "ai_level": 0,
                "pvp_rating": 0,
                "pvp_title": "",
                "head_id": 0,
                "org_id": 0,
                "org_name": "",
                "org_rank_name": "",
                "org_rank_id": 6,
                "source": "stub"
            })
            self.save_character_info(char_info)

    def save_character_info(self, char_info):
        if char_info["dimension"] != self.bot.dimension:
            return
        with self.db.pool.get_connection() as conn:
            with conn.cursor() as cur:
                # cur.execute("DELETE FROM player WHERE char_id = ?", [char_info["char_id"]])

                insert_sql = """
                    REPLACE INTO player (char_id, name, first_name, last_name, level, 
                    breed, gender, faction, profession, profession_title, ai_rank, ai_level, 
                    org_id, org_name, org_rank_name, org_rank_id, dimension, head_id,
                    pvp_rating, pvp_title, source, last_updated)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """

                cur.execute(insert_sql,
                            [char_info["char_id"], char_info["name"], char_info["first_name"], char_info["last_name"],
                             char_info["level"], char_info["breed"],
                             char_info["gender"], char_info["faction"], char_info["profession"],
                             char_info["profession_title"], char_info["ai_rank"], char_info["ai_level"],
                             char_info["org_id"], char_info["org_name"], char_info["org_rank_name"],
                             char_info["org_rank_id"], char_info["dimension"], char_info["head_id"],
                             char_info["pvp_rating"], char_info["pvp_title"], char_info["source"], int(time.time())])

    def get_from_database(self, char_id=None, char_name=None):
        if char_id:
            return self.db.query_single(
                "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                "FROM player WHERE char_id = ?", [char_id])
        elif char_name:
            return self.db.query_single(
                "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                "FROM player WHERE name = ?", [char_name])
        else:
            return None

    def update(self, conn, packet):
        # don't update if we didn't get a valid response
        if packet.char_id == 4294967295:
            return
        self.updates.append(packet)

    @timerevent(budatime="1min", description="Save player changes", is_hidden=True)
    def batch_update(self, event_type, event_data):
        update = "UPDATE player SET name = ? WHERE char_id = ?"
        insert = "INSERT IGNORE INTO player ( char_id, name, first_name, last_name, level, breed, gender, faction, " \
                 "profession, profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, " \
                 "dimension, head_id, pvp_rating, pvp_title, source, last_updated) " \
                 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
        updates, inserts = [], []
        with self.db.pool.get_connection() as conn:
            with conn.cursor(dictionary=True) as cur:
                for packet in self.updates:
                    cur.execute(
                        "SELECT char_id, name, first_name, last_name, level, breed, gender, faction, profession, "
                        "profession_title, ai_rank, ai_level, org_id, org_name, org_rank_name, org_rank_id, "
                        "dimension, head_id, pvp_rating, pvp_title, source, last_updated "
                        "FROM player WHERE char_id = ?",
                        [packet.char_id])
                    data = cur.fetchone()
                    character = None
                    if data:
                        character = DictObject(data)
                    if character:
                        if character.name != packet.name:
                            updates.append((packet.name, packet.char_id))
                    else:
                        inserts.append((packet.char_id, packet.name, "", "", 0, "", "", "", "", "", "", 0, 0, "", "", 6,
                                        self.bot.dimension, 0, 0, "", "chat_server", int(time.time())))
                if inserts:
                    cur.executemany(insert, inserts)
                if updates:
                    cur.executemany(update, updates)
        self.updates = []

    # noinspection SqlResolve
    def find_orgs(self, search):
        return self.db.query("SELECT DISTINCT org_name, org_id FROM all_orgs WHERE org_name <EXTENDED_LIKE=0> ?",
                             [search], extended_like=True)

    def get_pork_url(self, dimension, char_name):
        # Dont use SSL, as its rather slow compared to normal requests....
        # noinspection HttpUrlsUsage
        return f"http://people.anarchy-online.com/character/bio/d/{dimension}/name/{char_name}/bio.xml?data_type=json"
