import base64
import hashlib
import struct
from datetime import datetime


class WriteStreamWrapper:
    def __init__(self, stream, stream_is_closable):
        self.stream = stream
        self.stream_is_closable = stream_is_closable

    def write(self, data):
        self.stream.write(data)

    def write_string(self, string):
        str_bytes = string.encode('utf-8')
        str_header = len(str_bytes).to_bytes(1, byteorder='little', signed=False)
        str_header = b'\x0b' + str_header
        self.stream.write(str_header)
        self.stream.write(str_bytes)

    def write_byte(self, byte):
        self.stream.write(struct.pack('B', byte))

    def write_short(self, short):
        self.stream.write(struct.pack('<H', short))

    def write_integer(self, integer):
        self.stream.write(struct.pack('<I', integer))

    def write_long(self, low, high):
        self.stream.write(struct.pack('<II', low, high))

    def write_long_with_big_int(self, bigint):
        low = bigint & 0xffffffff
        high = bigint >> 32
        self.write_long(low, high)

    def write_dot_net_date_ticks(self, date):
        weird_constant = 429.4967296
        unix_ticks = (date.timestamp() + 62135596800)
        tmp = unix_ticks / weird_constant
        high = int(tmp)
        low = int((tmp - high) * weird_constant * 10000000)
        self.write_long(low, high)

    def write_osr_data2(self, replay_data: str, mods: int):
        data = {
            'gameMode': 0,
            'beatmapHash': 'beatmap_hash',
            'score': {
                'username': '',
                'maxcombo': 0,
                'count300': 0,
                'count100': 0,
                'count50': 0,
                'countgeki': 0,
                'countkatu': 0,
                'countmiss': 0,
                'score': 0,
                'rank': '',
                'perfect': True,
                'enabled_mods': mods,
                'date': '2024-03-05 12:15:36.000',
                'score_id': 0
            },
            'replayData': replay_data
        }

        self.write_byte(data['gameMode'])
        self.write_integer(20241228)
        self.write_string(data['beatmapHash'])
        self.write_string('Anonymous')
        replay_hash_data = f"{data['score']['maxcombo']}osu{data['score']['username']}{data['beatmapHash']}{data['score']['score']}{data['score']['rank']}"
        replay_hash = hashlib.md5(replay_hash_data.encode()).hexdigest()
        self.write_string(replay_hash)
        for key in ['count300', 'count100', 'count50', 'countgeki', 'countkatu', 'countmiss']:
            val = data['score'][key]
            if val is not None:
                self.write_short(int(val))
            else:
                self.write_short(0)
        self.write_integer(int(data['score']['score']))
        self.write_short(int(data['score']['maxcombo']))
        self.write_byte(int(data['score']['perfect']))
        self.write_integer(int(data['score']['enabled_mods']))
        self.write_string('')
        replay_date = self.parse_date(str(data['score']['date']))
        self.write_dot_net_date_ticks(replay_date)

        replay_data_decoded = base64.b64decode(data['replayData'])
        self.write_integer(len(replay_data_decoded))
        self.write(replay_data_decoded)

        self.write_long_with_big_int(int(data['score']['score_id']))

    @staticmethod
    def parse_date(date_str):
        if 'Z' in date_str and '+' in date_str:
            date_str = date_str.replace('Z', '')
        elif 'Z' in date_str:
            date_str = date_str.replace('Z', '+00:00')
        elif ' ' in date_str:
            date_str = date_str.replace(' ', 'T')

        return datetime.fromisoformat(date_str)

    def end(self):
        if self.stream_is_closable:
            self.stream.close()
