"""
Vistas del foro. Diseño integrado con NovaWoW (usa los partials del sitio).

Identidad del autor = cuenta de juego en sesión (``username`` / ``account_id``).
Permisos: ver `permissions.py`.
"""

import math

from django.conf import settings
from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.views.decorators.csrf import csrf_protect

from . import db
from . import permissions as perm
from . import sanitize
from .forms import TopicForm, ReplyForm, EditPostForm

MIN_TEXT_LEN = 5


# --------------------------------------------------------------------------
# Utilidades
# --------------------------------------------------------------------------

def _paginate(total, page, per_page):
    total_pages = max(1, math.ceil(total / per_page)) if per_page else 1
    page = max(1, min(page, total_pages))
    offset = (page - 1) * per_page
    return {
        'page': page,
        'total_pages': total_pages,
        'has_prev': page > 1,
        'has_next': page < total_pages,
        'prev_page': page - 1,
        'next_page': page + 1,
        'page_range': range(1, total_pages + 1),
        'total': total,
    }, offset


def _base_context(request, extra=None):
    ctx = dict(perm.forum_context(request))
    if extra:
        ctx.update(extra)
    return ctx


def _require_login(request):
    if not perm.is_authenticated(request):
        return redirect('login')
    return None


def _topic_locked_for(request, topic_id):
    """True si el tema está bloqueado y el usuario no es moderador."""
    if perm.is_moderator(request):
        return False
    topic = db.get_topic(topic_id, include_hidden=True)
    return bool(topic and topic['locked'])


# --------------------------------------------------------------------------
# Navegación
# --------------------------------------------------------------------------

def index(request):
    categories = db.get_index(include_hidden=perm.is_moderator(request))
    return render(request, 'forum/index.html', _base_context(request, {
        'categories': categories,
    }))


def view_forum(request, forum_id, page=1):
    is_mod = perm.is_moderator(request)
    forum = db.get_forum(forum_id, include_hidden=is_mod)
    if not forum:
        messages.error(request, 'Foro no encontrado.')
        return redirect('app_forum')

    total = db.count_topics(forum_id)
    pag, offset = _paginate(total, page, settings.FORUM_TOPICS_PER_PAGE)
    topics = db.get_topics(forum_id, offset, settings.FORUM_TOPICS_PER_PAGE)
    for t in topics:
        t['display_poster'] = db.get_display_name(t['poster_id']) or t['poster']

    return render(request, 'forum/forum.html', _base_context(request, {
        'forum': forum,
        'topics': topics,
        'pagination': pag,
        'has_views': db.has_views_column(),
    }))


def search(request):
    query = (request.GET.get('q') or '').strip()
    try:
        page = int(request.GET.get('page', 1))
    except (TypeError, ValueError):
        page = 1
    results = []
    pag = None
    if len(query) >= 2:
        total = db.count_search_topics(query)
        pag, offset = _paginate(total, page, settings.FORUM_TOPICS_PER_PAGE)
        results = db.search_topics(query, offset, settings.FORUM_TOPICS_PER_PAGE)
        for r in results:
            r['display_poster'] = db.get_display_name(r['poster_id']) or r['poster']
    return render(request, 'forum/search.html', _base_context(request, {
        'query': query,
        'results': results,
        'pagination': pag,
    }))


def view_topic(request, topic_id, page=1):
    is_mod = perm.is_moderator(request)
    topic = db.get_topic(topic_id, include_hidden=is_mod)
    if not topic:
        messages.error(request, 'Tema no encontrado.')
        return redirect('app_forum')

    # El foro padre debe ser visible (salvo moderador); si no, no se accede al
    # tema por URL directa aunque no aparezca en los listados.
    forum = db.get_forum(topic['forum_id'], include_hidden=is_mod)
    if not forum:
        messages.error(request, 'Tema no disponible.')
        return redirect('app_forum')

    # Contador de visitas (solo si la columna existe)
    if db.has_views_column():
        db.increment_views(topic_id)
        topic['views'] = (topic.get('views') or 0) + 1
        topic['has_views'] = True
    else:
        topic['has_views'] = False

    total = db.count_posts(topic_id, include_deleted=is_mod)
    pag, offset = _paginate(total, page, settings.FORUM_POSTS_PER_PAGE)
    posts = db.get_posts(topic_id, offset, settings.FORUM_POSTS_PER_PAGE, is_mod)

    # Marcar como leído
    db.mark_read(perm.get_account_id(request), topic_id)

    # Permisos y datos de autor por post
    for p in posts:
        p['can_edit'] = perm.can_edit_post(request, p)
        p['author'] = db.get_author_info(p['poster_id'])

    return render(request, 'forum/topic.html', _base_context(request, {
        'topic': topic,
        'forum': forum,
        'posts': posts,
        'pagination': pag,
        'can_reply': perm.can_post(request) and (not topic['locked'] or is_mod),
        'move_forums': db.list_forums_for_move(topic['forum_id']) if is_mod else [],
        'reply_form': ReplyForm(),
    }))


# --------------------------------------------------------------------------
# Crear tema / responder
# --------------------------------------------------------------------------

def create_topic(request, forum_id):
    login = _require_login(request)
    if login:
        return login
    forum = db.get_forum(forum_id, include_hidden=perm.is_moderator(request))
    if not forum:
        messages.error(request, 'Foro no encontrado.')
        return redirect('app_forum')
    return render(request, 'forum/create_topic.html', _base_context(request, {
        'forum': forum,
        'form': TopicForm(),
    }))


@csrf_protect
def submit_create_topic(request, forum_id):
    login = _require_login(request)
    if login:
        return login
    if request.method != 'POST':
        return redirect('forum_create_topic', forum_id=forum_id)

    forum = db.get_forum(forum_id, include_hidden=perm.is_moderator(request))
    if not forum:
        messages.error(request, 'Foro no encontrado.')
        return redirect('app_forum')

    name = (request.POST.get('name') or '').strip()
    text = sanitize.clean_post_html((request.POST.get('text') or '').strip())
    if not name or sanitize.plain_length(text) < MIN_TEXT_LEN:
        messages.error(request, 'El título y el contenido (mínimo 5 caracteres) son obligatorios.')
        return redirect('forum_create_topic', forum_id=forum_id)
    if len(name) > 45:
        messages.error(request, 'El título no puede superar los 45 caracteres.')
        return redirect('forum_create_topic', forum_id=forum_id)

    poster = request.session.get('username')
    poster_id = perm.get_account_id(request)
    topic_id = db.create_topic(forum_id, name, poster, poster_id, text)
    messages.success(request, 'Tema creado.')
    return redirect('forum_topic_p1', topic_id=topic_id)


@csrf_protect
def reply_to_topic(request, topic_id):
    login = _require_login(request)
    if login:
        return login
    if request.method != 'POST':
        return redirect('forum_topic_p1', topic_id=topic_id)

    is_mod = perm.is_moderator(request)
    topic = db.get_topic(topic_id)
    if not topic:
        messages.error(request, 'Tema no encontrado.')
        return redirect('app_forum')
    # El foro padre debe ser visible (salvo moderador)
    if not db.get_forum(topic['forum_id'], include_hidden=is_mod):
        messages.error(request, 'Tema no disponible.')
        return redirect('app_forum')
    if topic['locked'] and not is_mod:
        messages.error(request, 'El tema está bloqueado.')
        return redirect('forum_topic_p1', topic_id=topic_id)

    text = sanitize.clean_post_html((request.POST.get('text') or '').strip())
    if sanitize.plain_length(text) < MIN_TEXT_LEN:
        messages.error(request, 'El mensaje debe tener al menos 5 caracteres.')
        return redirect('forum_topic_p1', topic_id=topic_id)

    db.create_post(topic_id, request.session.get('username'), perm.get_account_id(request), text)

    # Ir a la última página
    total = db.count_posts(topic_id)
    last_page = max(1, math.ceil(total / settings.FORUM_POSTS_PER_PAGE))
    url = reverse('forum_topic', kwargs={'topic_id': topic_id, 'page': last_page})
    return redirect(f'{url}#post-bottom')


# --------------------------------------------------------------------------
# Editar / borrar / restaurar posts
# --------------------------------------------------------------------------

def edit_post(request, post_id):
    login = _require_login(request)
    if login:
        return login
    post = db.get_post(post_id)
    if not post:
        messages.error(request, 'Post no encontrado.')
        return redirect('app_forum')
    if not perm.can_edit_post(request, post):
        messages.error(request, 'No tienes permiso para editar este post.')
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    return render(request, 'forum/edit_post.html', _base_context(request, {
        'post': post,
        'form': EditPostForm(initial={'text': post['text']}),
    }))


@csrf_protect
def update_post(request, post_id):
    login = _require_login(request)
    if login:
        return login
    post = db.get_post(post_id)
    if not post:
        messages.error(request, 'Post no encontrado.')
        return redirect('app_forum')
    if not perm.can_edit_post(request, post):
        messages.error(request, 'No tienes permiso para editar este post.')
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    if _topic_locked_for(request, post['topic_id']):
        messages.error(request, 'El tema está bloqueado.')
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    if request.method != 'POST':
        return redirect('forum_edit_post', post_id=post_id)

    text = sanitize.clean_post_html((request.POST.get('text') or '').strip())
    if sanitize.plain_length(text) < MIN_TEXT_LEN:
        messages.error(request, 'El mensaje debe tener al menos 5 caracteres.')
        return redirect('forum_edit_post', post_id=post_id)

    db.update_post(post_id, text)
    messages.success(request, 'Post actualizado.')
    return redirect('forum_topic_p1', topic_id=post['topic_id'])


@csrf_protect
def delete_post(request, post_id):
    login = _require_login(request)
    if login:
        return login
    post = db.get_post(post_id)
    if not post:
        messages.error(request, 'Post no encontrado.')
        return redirect('app_forum')
    if not perm.can_edit_post(request, post):
        messages.error(request, 'No tienes permiso.')
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    if _topic_locked_for(request, post['topic_id']):
        messages.error(request, 'El tema está bloqueado.')
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    if request.method != 'POST':
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    db.set_post_deleted(post_id, True)
    messages.success(request, 'Post borrado.')
    return redirect('forum_topic_p1', topic_id=post['topic_id'])


@csrf_protect
def restore_post(request, post_id):
    if not perm.is_moderator(request):
        return redirect('app_forum')
    post = db.get_post(post_id)
    if not post:
        return redirect('app_forum')
    if request.method != 'POST':
        return redirect('forum_topic_p1', topic_id=post['topic_id'])
    db.set_post_deleted(post_id, False)
    messages.success(request, 'Post restaurado.')
    return redirect('forum_topic_p1', topic_id=post['topic_id'])


# --------------------------------------------------------------------------
# Moderación de temas
# --------------------------------------------------------------------------

def _mod_only(request):
    if not perm.is_moderator(request):
        messages.error(request, 'Acción solo para moderadores.')
        return redirect('app_forum')
    return None


def _mod_post_only(request):
    """Exige moderador y método POST (CSRF)."""
    block = _mod_only(request)
    if block:
        return block
    if request.method != 'POST':
        return redirect('app_forum')
    return None


@csrf_protect
def lock_topic(request, topic_id):
    block = _mod_post_only(request)
    if block:
        return block
    db.set_topic_flag(topic_id, 'locked', True)
    messages.success(request, 'Tema bloqueado.')
    return redirect('forum_topic_p1', topic_id=topic_id)


@csrf_protect
def unlock_topic(request, topic_id):
    block = _mod_post_only(request)
    if block:
        return block
    db.set_topic_flag(topic_id, 'locked', False)
    messages.success(request, 'Tema desbloqueado.')
    return redirect('forum_topic_p1', topic_id=topic_id)


@csrf_protect
def toggle_sticky(request, topic_id):
    block = _mod_post_only(request)
    if block:
        return block
    topic = db.get_topic(topic_id, include_hidden=True)
    if topic:
        db.set_topic_flag(topic_id, 'sticky', not topic['sticky'])
        messages.success(request, 'Tema fijado.' if not topic['sticky'] else 'Tema no fijado.')
    return redirect('forum_topic_p1', topic_id=topic_id)


@csrf_protect
def delete_topic(request, topic_id):
    block = _mod_post_only(request)
    if block:
        return block
    topic = db.get_topic(topic_id, include_hidden=True)
    db.set_topic_flag(topic_id, 'deleted', True)
    messages.success(request, 'Tema borrado.')
    if topic:
        return redirect('forum_view_p1', forum_id=topic['forum_id'])
    return redirect('app_forum')


@csrf_protect
def restore_topic(request, topic_id):
    block = _mod_post_only(request)
    if block:
        return block
    db.set_topic_flag(topic_id, 'deleted', False)
    messages.success(request, 'Tema restaurado.')
    return redirect('forum_topic_p1', topic_id=topic_id)


@csrf_protect
def move_topic(request, topic_id):
    block = _mod_only(request)
    if block:
        return block
    if request.method != 'POST':
        return redirect('forum_topic_p1', topic_id=topic_id)
    try:
        new_forum_id = int(request.POST.get('forum_id', ''))
    except (TypeError, ValueError):
        messages.error(request, 'Foro destino no válido.')
        return redirect('forum_topic_p1', topic_id=topic_id)
    if not db.get_forum(new_forum_id, include_hidden=True):
        messages.error(request, 'Foro destino no válido.')
        return redirect('forum_topic_p1', topic_id=topic_id)
    db.move_topic(topic_id, new_forum_id)
    messages.success(request, 'Tema movido.')
    return redirect('forum_topic_p1', topic_id=topic_id)


# --------------------------------------------------------------------------
# Perfil público
# --------------------------------------------------------------------------

def profile(request, username):
    from django.db import connections
    stats = {'username': username, 'post_count': 0, 'topic_count': 0, 'joindate': None}
    with connections['acore_web'].cursor() as cursor:
        cursor.execute(
            "SELECT COUNT(*) FROM forum_posts WHERE poster = %s AND deleted = 0", [username]
        )
        stats['post_count'] = cursor.fetchone()[0]
        cursor.execute(
            "SELECT COUNT(*) FROM forum_topics WHERE poster = %s AND deleted = 0", [username]
        )
        stats['topic_count'] = cursor.fetchone()[0]
    try:
        with connections['acore_auth'].cursor() as cursor:
            cursor.execute("SELECT joindate FROM account WHERE username = %s", [username])
            row = cursor.fetchone()
            if row:
                stats['joindate'] = row[0]
    except Exception:
        pass
    return render(request, 'forum/profile.html', _base_context(request, {
        'profile': stats,
    }))
