# -*- coding: utf-8 -*-
"""Helpers conservadores de storage/migração para o addon.

Blocos pesados e coesos saíram do main.py para reduzir risco em manutenção futura.
A navegação/rotas continuam no main.py de propósito.
"""
import hashlib
import json
import os
import re
import time
import unicodedata
import urllib.parse

import xbmc
import xbmcvfs


def _log(ctx, message, level=xbmc.LOGDEBUG):
    fn = (ctx or {}).get('log_fn')
    if not fn:
        return
    try:
        fn(message, level)
    except TypeError:
        try:
            fn(message)
        except Exception:
            pass
    except Exception:
        pass


def _path_exists(path):
    try:
        if path and os.path.exists(path):
            return True
    except Exception:
        pass
    try:
        return bool(path and xbmcvfs.exists(path))
    except Exception:
        return False


def ensure_profile_dir(profile_dir):
    try:
        os.makedirs(profile_dir, exist_ok=True)
    except Exception:
        try:
            if not xbmcvfs.exists(profile_dir):
                xbmcvfs.mkdirs(profile_dir)
        except Exception:
            pass


def ensure_parent_dir(path):
    try:
        parent = os.path.dirname(path)
        if parent:
            os.makedirs(parent, exist_ok=True)
    except Exception:
        try:
            parent = os.path.dirname(path)
            if parent and not xbmcvfs.exists(parent):
                xbmcvfs.mkdirs(parent)
        except Exception:
            pass


def safe_delete_file(path):
    deleted = False
    for candidate in (path, f"{path}.tmp" if path else ''):
        if not candidate:
            continue
        try:
            if os.path.exists(candidate):
                os.remove(candidate)
                deleted = True
                continue
        except Exception:
            pass
        try:
            if xbmcvfs.exists(candidate):
                if xbmcvfs.delete(candidate):
                    deleted = True
        except Exception:
            pass
    return deleted


def safe_move_file(src, dst):
    try:
        os.replace(src, dst)
        return True
    except Exception:
        pass
    try:
        if os.path.exists(dst):
            os.remove(dst)
    except Exception:
        pass
    try:
        os.rename(src, dst)
        return True
    except Exception:
        pass
    try:
        if xbmcvfs.exists(dst):
            xbmcvfs.delete(dst)
    except Exception:
        pass
    try:
        return bool(xbmcvfs.rename(src, dst))
    except Exception:
        return False


def atomic_write_text(path, text, profile_dir=None, encoding='utf-8'):
    if profile_dir:
        ensure_profile_dir(profile_dir)
    ensure_parent_dir(path)
    temp_path = f"{path}.tmp.{os.getpid()}.{int(time.time() * 1000)}"
    try:
        with open(temp_path, 'w', encoding=encoding) as fh:
            fh.write(text)
            try:
                fh.flush()
                os.fsync(fh.fileno())
            except Exception:
                pass
        if not safe_move_file(temp_path, path):
            raise IOError(f'Falha ao mover arquivo temporário para {path}')
        return True
    finally:
        try:
            if os.path.exists(temp_path):
                os.remove(temp_path)
        except Exception:
            pass


def atomic_write_json(path, data, profile_dir=None, ensure_ascii=False, indent=None, log_fn=None):
    try:
        payload = json.dumps(data, ensure_ascii=ensure_ascii, indent=indent)
        return atomic_write_text(path, payload, profile_dir=profile_dir, encoding='utf-8')
    except Exception as exc:
        if log_fn:
            try:
                log_fn(f'Falha ao serializar JSON {path}: {exc}', xbmc.LOGDEBUG)
            except Exception:
                pass
        return False


def storage_identity_from_values(base_url='', username=''):
    base_url = (base_url or '').strip().rstrip('/').lower()
    username = (username or '').strip().lower()
    return f'{base_url}|{username}' if (base_url or username) else 'default'


def scope_name_from_identity(identity, base_url='', username=''):
    identity = (identity or '').strip() or 'default'
    if identity == 'default':
        return 'default'
    if not (base_url or username):
        try:
            base_url, username = identity.split('|', 1)
        except Exception:
            base_url, username = '', ''
    try:
        parsed = urllib.parse.urlparse((base_url or '').strip())
        host = parsed.netloc or parsed.path or 'server'
    except Exception:
        host = (base_url or '').strip() or 'server'
    safe_host = re.sub(r'[^A-Za-z0-9._-]+', '_', (host or 'server')).strip('._-') or 'server'
    safe_user = re.sub(r'[^A-Za-z0-9._-]+', '_', (username or 'user')).strip('._-') or 'user'
    digest = hashlib.sha1(identity.encode('utf-8')).hexdigest()[:10]
    return f'{safe_host}__{safe_user}__{digest}'


def server_slot_scope_name(get_current_server_choice_fn, server_idx=None):
    try:
        idx = get_current_server_choice_fn() if server_idx is None else int(server_idx)
    except Exception:
        idx = 0
    if idx < 0:
        idx = 0
    return f'server_{idx + 1}'


def server_slot_storage_path(profile_dir, filename, get_current_server_choice_fn, server_idx=None):
    return os.path.join(profile_dir, 'server_slot_storage', server_slot_scope_name(get_current_server_choice_fn, server_idx), filename)


def scoped_storage_path_from_values(scoped_storage_root, filename, base_url='', username=''):
    identity = storage_identity_from_values(base_url, username)
    if identity == 'default':
        return ''
    scope_name = scope_name_from_identity(identity, base_url, username)
    return os.path.join(scoped_storage_root, scope_name, filename)


def load_json_file(path, default=None, profile_dir=None, log_fn=None):
    try:
        if profile_dir:
            ensure_profile_dir(profile_dir)
        ensure_parent_dir(path)
        if not _path_exists(path):
            return default
        with open(path, 'r', encoding='utf-8') as fh:
            return json.load(fh)
    except Exception as exc:
        if log_fn:
            try:
                log_fn(f'Falha ao ler JSON {path}: {exc}', xbmc.LOGDEBUG)
            except Exception:
                pass
        return default


def save_json_file(path, data, profile_dir=None, log_fn=None):
    try:
        return atomic_write_json(path, data, profile_dir=profile_dir, ensure_ascii=False, log_fn=log_fn)
    except Exception as exc:
        if log_fn:
            try:
                log_fn(f'Falha ao salvar JSON {path}: {exc}', xbmc.LOGDEBUG)
            except Exception:
                pass
        return False


def normalize_sort_text(text):
    try:
        text = str(text or '').strip().lower()
        text = unicodedata.normalize('NFKD', text)
        return ''.join(ch for ch in text if not unicodedata.combining(ch))
    except Exception:
        return str(text or '').strip().lower()


def maybe_migrate_server_slot_json(ctx, filename, legacy_path, default_payload, server_idx=None, base_url=None, username=None, log_label='dados'):
    target_path = ctx['server_slot_storage_path_fn'](filename, server_idx)
    try:
        ensure_profile_dir(ctx['profile_dir'])
        ensure_parent_dir(target_path)
        if _path_exists(target_path):
            return target_path

        base_url = (ctx['get_base_url_fn']() if base_url is None else base_url) or ''
        username = ((ctx.get('username') or '') if username is None else username) or ''

        candidate_paths = []
        current_scoped_path = ctx['scoped_storage_path_from_values_fn'](filename, base_url, username)
        if current_scoped_path and current_scoped_path != target_path and _path_exists(current_scoped_path):
            candidate_paths.append(current_scoped_path)
        if legacy_path and _path_exists(legacy_path) and legacy_path not in candidate_paths:
            candidate_paths.append(legacy_path)

        for source_path in candidate_paths:
            data = ctx['load_json_fn'](source_path, default_payload)
            if data is None:
                continue
            if ctx['save_json_fn'](target_path, data):
                _log(ctx, f'{log_label} migrados para escopo por servidor: {target_path}')
                return target_path
    except Exception as exc:
        _log(ctx, f'Falha ao migrar {log_label} por servidor: {exc}', xbmc.LOGDEBUG)
    return target_path


def maybe_migrate_server_slot_file(ctx, filename, legacy_path, server_idx=None, base_url=None, username=None, log_label='arquivo'):
    target_path = ctx['server_slot_storage_path_fn'](filename, server_idx)
    try:
        ensure_profile_dir(ctx['profile_dir'])
        ensure_parent_dir(target_path)
        if _path_exists(target_path):
            return target_path

        base_url = (ctx['get_base_url_fn']() if base_url is None else base_url) or ''
        username = ((ctx.get('username') or '') if username is None else username) or ''

        candidate_paths = []
        current_scoped_path = ctx['scoped_storage_path_from_values_fn'](filename, base_url, username)
        if current_scoped_path and current_scoped_path != target_path and _path_exists(current_scoped_path):
            candidate_paths.append(current_scoped_path)
        if legacy_path and _path_exists(legacy_path) and legacy_path not in candidate_paths:
            candidate_paths.append(legacy_path)

        for source_path in candidate_paths:
            try:
                if xbmcvfs.copy(source_path, target_path):
                    _log(ctx, f'{log_label} migrado para escopo por servidor: {target_path}')
                    return target_path
            except Exception:
                continue
    except Exception as exc:
        _log(ctx, f'Falha ao migrar {log_label} por servidor: {exc}', xbmc.LOGDEBUG)
    return target_path
