# Foobar - a sport competition management software
# Copyright (c) 2026 Felipe Cuqueto Piekarz
# Copyright (c) 2026 Luiz Soneghet Nascimento
# Copyright (c) 2026 Rodolfo Ribeiro Gomes
#
# This file is part of Foobar.
#
# Foobar is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Foobar is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Foobar.  If not, see <https://www.gnu.org/licenses/>.

from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.http import HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden, HttpResponseGone, HttpResponseBadRequest
from django.utils.crypto import get_random_string
from django.utils import timezone
from django.contrib.auth.decorators import login_required, permission_required

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from .models import Edicao, Torneio, ConfiguracaoTime, Time, ChaveInscricao, AcessoChaveInscricao, Integrante, Partida
from .forms import CriarTorneioForm, CriarConfiguracaoTimeForm, CriarConfiguracaoTimeNaipeForm, CriarChaveAcessoForm, CriarIntegranteForm

def index(request):
    lista_torneios = Torneio.objects.order_by("-nome")
    contexto = {"lista_torneios": lista_torneios}
    return render(request, "torneios/indice.html", contexto)

@login_required
def create(request, edicao_id = 1, torneio_id = -1):
    edicao = get_object_or_404(Edicao, pk=edicao_id)
 
    if timezone.now().date() > edicao.fim_data:
        return HttpResponseForbidden(_("O evento já concluiu."))
    
    if request.method == "POST":
        if torneio_id > 0:
            # editar
            torneio = get_object_or_404(Torneio, pk=torneio_id)
            form = CriarTorneioForm(request.POST, instance=torneio)
        else:
            # criar
            form = CriarTorneioForm(request.POST)
            form.set_edicao(edicao_id)
        if form.is_valid():
            torneio = form.save()
            return HttpResponseRedirect(reverse("torneio:show", args=(torneio.id,)))
    else:
        if torneio_id > 0:
            torneio = get_object_or_404(Torneio, pk=torneio_id)
            form = CriarTorneioForm(instance=torneio)
        else:
            form = CriarTorneioForm()

    contexto = {"edicao": edicao, "criar_torneio_form":form}
    return render(request, "torneios/criar.html", contexto)

def show(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    if torneio.tem_configuracao_time():
        configuracoes_time = torneio.configuracaotime
    else:
        configuracoes_time = None
    contexto = {"torneio": torneio, "configuracoes_time":configuracoes_time}
    return render(request, "torneios/mostrar.html", contexto)

@login_required
def generate_bracket(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    torneio.gera_partidas_chaveamento()
    tipo_chaveamento = request.GET.get('tipo')
    
    if request.method == "POST":
        pass
    else:
        pass
    return HttpResponseNotFound(_("Pendente de implementação"))

@login_required
def create_team_settings(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    if torneio.naipe == 'T':
        classe_form = CriarConfiguracaoTimeForm
    else:
        classe_form = CriarConfiguracaoTimeNaipeForm
    if request.method == "POST":
        if torneio.tem_configuracao_time():
            form = classe_form(request.POST, instance=torneio.configuracaotime)
        else:
            form = classe_form(request.POST)
        if form.is_valid():
            nova_configuracao = form.save()
            nova_configuracao.torneio = torneio
            if torneio.naipe == 'M':
                nova_configuracao.masculino_minimo = nova_configuracao.total_minimo
                nova_configuracao.masculino_maximo = nova_configuracao.total_maximo
            elif torneio.naipe == 'F':
                nova_configuracao.feminino_minimo = nova_configuracao.total_minimo
                nova_configuracao.feminino_maximo = nova_configuracao.total_maximo
            nova_configuracao.save()
            return HttpResponseRedirect(reverse("torneio:show", args=(torneio.id,)))
    else:
        if torneio.tem_configuracao_time():
            form = classe_form(instance=torneio.configuracaotime)
        else:
            form = classe_form()

    contexto = {"torneio": torneio, "criar_configuracao_time_form":form}
    return render(request, "torneios/criar_configuracao_time.html", contexto)

@login_required
def generate_team_application_link(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    if not torneio.tem_configuracao_time():
        return HttpResponseRedirect(reverse("torneio:create-team-settings", args=(torneio.id,)))
    if torneio.partidas.count() > 0:
        return HttpResponseBadRequest(_('Já foi feito um chaveamento das partidas sem esse time.'))

    if request.method == "POST":
        form = CriarChaveAcessoForm(request.POST)
        if form.is_valid():
            nome_time = form.cleaned_data['nome_time']
            configuracoes_time = ConfiguracaoTime.objects.get(torneio_id=torneio_id)
            time = Time.objects.filter(nome=nome_time, configuracao=configuracoes_time)
            if time:
                form.add_error(field="nome_time", error=ValidationError(
                    _('Este time "%(name)s" já existe'), params={"name": nome_time},
                ))
            else:
                time = Time(nome=nome_time, configuracao=configuracoes_time)
                time.save()
                codigo = str(time.id^torneio.id)+'_'+str(time.id)+'t'+str(torneio.id)+'c'+get_random_string(length=40)
                chave = ChaveInscricao(time=time, codigo=codigo)
                chave.save()
                return HttpResponseRedirect(reverse("torneio:generate-team-application-link", args=(torneio.id,)))
    else:
        form = CriarChaveAcessoForm()

    times_inscritos = Time.objects.filter(configuracao__torneio_id=torneio_id)
    links_gerados = ChaveInscricao.objects.filter(time__configuracao__torneio_id=torneio_id)
    for link in links_gerados:
        link.url = request.build_absolute_uri(reverse("torneio:access-team-application-link", args=(torneio.id,link.codigo)))
    contexto = {"torneio": torneio, "times_inscritos":times_inscritos, "links_gerados":links_gerados, "criar_chave_acesso_form":form}
    return render(request, "torneios/gerar_links_inscricao.html", contexto)

def show_team(request, torneio_id, time_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    time = get_object_or_404(Time, pk=time_id)
    contexto = {"torneio":torneio, "time":time}
    return render(request, "torneios/mostrar_time.html", contexto)

@login_required
def review_teams(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)

    times_inscritos = Time.objects.filter(configuracao__torneio_id=torneio_id)
    contexto = {"torneio": torneio, "times_inscritos":times_inscritos}
    return render(request, "torneios/analisar_inscricoes.html", contexto)

@login_required
def review_team(request, torneio_id, time_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    time = get_object_or_404(Time, pk=time_id)
    if request.method != 'POST' or '_method' not in request.POST or request.POST['_method'] != 'UPDATE':
        return HttpResponseNotFound(_("<h1>Página não encontrada</h1>"))
    
    if not time.conforme:
        return HttpResponseForbidden(_("A composição do time está inválida"))

    aprovado = request.POST['aprovado'] == '1'
    time.aprovado = aprovado
    time.aprovacao_por = request.user
    time.save()
    if 'prox' in request.POST:
        return HttpResponseRedirect(request.POST['prox'])
    return HttpResponseRedirect(reverse("torneio:show-team", args=(torneio.id,time.id)))

def access_team_application_link(request, torneio_id, segredo):
    chave = ChaveInscricao.objects.filter(time__configuracao__torneio_id=torneio_id, codigo=segredo)
    if len(chave) <= 0:
        return HttpResponseRedirect(reverse("torneio:show", args=(torneio_id,)))

    torneio = get_object_or_404(Torneio, pk=torneio_id)
    chave = chave[0]

    acesso = AcessoChaveInscricao(horario=timezone.now(), chave_inscricao=chave)
    acesso.ip = request.META.get('REMOTE_ADDR')
    acesso.save()

    if not torneio.inscricoes_abertas:
        return HttpResponseForbidden(_("Inscrição não disponível"))
    elif not chave.ativa:
        return HttpResponseGone(_("Credencial inválida"))
    elif chave.time.aprovado:
        return HttpResponseForbidden(_("Inscrição já aprovada: não pode alterar time"))

    if request.method == "GET":
        form = CriarIntegranteForm()
        contexto = {"codigo":segredo, "time":chave.time, "criar_integrante_form":form}
        return render(request, "torneios/inscrever_time.html", contexto)
    elif request.method == "POST":
        form = CriarIntegranteForm(request.POST)
        if form.is_valid():
            nome = form.cleaned_data['nome']
            genero = request.POST['genero']
            
            integrante = Integrante.objects.filter(time = chave.time, nome = nome)
            if integrante:
                form.add_error(field="nome", error=ValidationError(
                    _('"%(name)s" já participa deste time'), params={"name": nome},
                ))
            else:
                integrante = Integrante.objects.filter(time__configuracao = chave.time.configuracao, nome = nome)
                if integrante:
                    form.add_error(field="nome", error=ValidationError(
                    _('"%(name)s" já participa de outro time: %(team)s'), params={"name": nome, "team": integrante[0].time.nome},
                ))
                else:
                    integrante = Integrante(time = chave.time, nome = nome, genero = genero)
                    integrante.save()
        
        contexto = {"codigo":segredo, "time":chave.time, "criar_integrante_form":form}
        return render(request, "torneios/inscrever_time.html", contexto)
    else:
        return HttpResponseNotFound(_("<h1>Página não encontrada</h1>"))

def remove_team_member(request, torneio_id, segredo, integrante_id):
    if request.method != 'POST' or request.POST['_method'] != 'DELETE':
        return HttpResponseNotFound(_("<h1>Página não encontrada</h1>"))

    chave = ChaveInscricao.objects.filter(time__configuracao__torneio_id=torneio_id, codigo=segredo)
    if len(chave) <= 0:
        return HttpResponseRedirect(reverse("torneio:show", args=(torneio_id,)))

    torneio = get_object_or_404(Torneio, pk=torneio_id)
    chave = chave[0]

    acesso = AcessoChaveInscricao(horario=timezone.now(), chave_inscricao=chave)
    acesso.ip = request.META.get('REMOTE_ADDR')
    acesso.save()

    if not torneio.inscricoes_abertas:
        return HttpResponseForbidden(_("Inscrição não disponível"))
    elif not chave.ativa:
        return HttpResponseGone(_("Credencial inválida"))
    elif chave.time.aprovado:
        return HttpResponseForbidden(_("Inscrição já aprovada: não pode alterar time"))

    integrante = get_object_or_404(Integrante, pk=integrante_id)
    if integrante.time_id != chave.time_id:
        return HttpResponseNotFound(_("Não há tal integrante"))
    
    integrante.delete()
    
    return HttpResponseRedirect(reverse("torneio:access-team-application-link", args=(torneio_id,segredo)))

def matches(request, torneio_id, edit_mode = None):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    partidas = Partida.objects.filter(torneio=torneio).order_by('numero')
    partidas_hierarquia = {}
    for p in partidas:
        grau = p.grau()
        if  grau in partidas_hierarquia:
            partidas_hierarquia[grau].append(p)
        else:
            partidas_hierarquia[grau] = [p]
    contexto = {"torneio":torneio, "partidas":partidas, "partidas_hierarquia":partidas_hierarquia}
    if edit_mode:
        contexto['edit_mode'] = edit_mode
    return render(request, "torneios/partidas.html", contexto)

@permission_required("torneio.change_partida")
def edit_bracket(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    partidas = Partida.objects.filter(torneio=torneio).order_by('numero')
    partidas_hierarquia = {}
    for p in partidas:
        grau = p.grau()
        if  grau in partidas_hierarquia:
            partidas_hierarquia[grau].append(p)
        else:
            partidas_hierarquia[grau] = [p]
    contexto = {"torneio":torneio, "partidas":partidas, "partidas_hierarquia":partidas_hierarquia, "edit_mode":'bracket'}
    return render(request, "torneios/partidas.html", contexto)

@permission_required("torneio.change_partida")
def swap_match_teams(request, torneio_id, partida_num):
    if request.method != 'POST':
        return HttpResponseForbidden(_("Método não permitido"))
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    partida = Partida.objects.filter(torneio=torneio, numero=partida_num)
    if not partida or partida.count() != 1:
        return HttpResponseNotFound(_("Partida inexistente"))
    partida = partida[0]
    partida.permutar_filhos()
    return HttpResponseRedirect(reverse("torneio:matches", args=(torneio.id, )))

@permission_required("torneio.change_partida")
def swap_matches(request, torneio_id):
    if request.method != 'POST':
        return HttpResponseForbidden(_("Método não permitido"))
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    if not 'partida1' in request.POST or not 'partida2' in request.POST:
        return HttpResponseForbidden(_("Faltam dados"))
    partidas = Partida.objects.filter(torneio=torneio, numero=request.POST['partida1'])
    if not partidas or partidas.count() != 1:
        return HttpResponseNotFound(_("Partida inexistente"))
    partida1 = partidas[0]
    partidas = Partida.objects.filter(torneio=torneio, numero=request.POST['partida2'])
    if not partidas or partidas.count() != 1:
        return HttpResponseNotFound(_("Partida inexistente"))
    partida2 = partidas[0]

    partida1.permutar_com_partida(partida2)
    
    return HttpResponseRedirect(reverse("torneio:matches", args=(torneio.id, )))

@permission_required("torneio.change_partida")
def swap_one_team_between_matches(request, torneio_id):
    if request.method != 'POST':
        return HttpResponseForbidden(_("Método não permitido"))
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    if not 'partida1' in request.POST or not 'partida2' in request.POST or not 'indice' in request.POST:
        return HttpResponseForbidden(_("Faltam dados"))
    indice = int(request.POST['indice'])
    if indice < 1 or indice > 2:
        return HttpResponseNotFound(_("Índice inexistente"))
    partidas = Partida.objects.filter(torneio=torneio, numero=request.POST['partida1'])
    if not partidas or partidas.count() != 1:
        return HttpResponseNotFound(_("Partida inexistente"))
    partida1 = partidas[0]
    partidas = Partida.objects.filter(torneio=torneio, numero=request.POST['partida2'])
    if not partidas or partidas.count() != 1:
        return HttpResponseNotFound(_("Partida inexistente"))
    partida2 = partidas[0]

    partida1.permutar_um_time_com_partida(partida2, indice)
    
    return HttpResponseRedirect(reverse("torneio:matches", args=(torneio.id, )))

@permission_required("torneio.definir_numero_time")
def assign_team_number(request, torneio_id):
    if request.method == 'GET':
        return matches(request, torneio_id, edit_mode='team_assignment')

    if request.method != 'POST':
        return HttpResponseForbidden(_("Método não permitido"))
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    
    times = torneio.inscricoes_aprovadas()
    qtd_times = times.count()

    numeros_de_time_ja_atribuidos = {}
    for time in times:
        if time.numero != 0:
            numeros_de_time_ja_atribuidos[time.numero] = time
    
    #numeros_a_atribuir = []
    times_alterados = []
    for numero_time in range(1, qtd_times+1):
        nome_campo = f'time_{numero_time}'
        if nome_campo in request.POST:
            id_time = int(request.POST[nome_campo])
            if numero_time in numeros_de_time_ja_atribuidos:
                time_anterior = numeros_de_time_ja_atribuidos[numero_time]
                nome_campo_do_anterior = f'time_anterior_{numero_time}'
                if not nome_campo_do_anterior in request.POST:
                    return HttpResponseBadRequest(_('Um dos números já foi atribuído a outro time'))
                if id_time == time_anterior.id:
                    continue # nada a alterar
                id_informado_time_anterior = int(request.POST[nome_campo_do_anterior])
                if id_informado_time_anterior != time_anterior.id:
                    return HttpResponseBadRequest(_('Um dos números já foi atribuído a outro time!'))
                # acertou o id do time anteriormente com esse número, logo deseja trocar mesmo
                time_anterior.numero = 0
                times_alterados.append(time_anterior)
                del numeros_de_time_ja_atribuidos[numero_time]
            #numeros_a_atribuir.append(numero_time)
            
            if id_time > 0:
                time = None # next(t for t in times if t.id == id_time)
                for t in times:
                    if t.id == id_time:
                        time = t
                        break
                if not time:
                    return HttpResponseBadRequest(_('Um dos times não existe!'))
                time.numero = numero_time
                # não salva até concluir conferência em todas as iterações
                times_alterados.append(time)
                numeros_de_time_ja_atribuidos[numero_time] = time
    
    for time in set(times_alterados):
        time.save()
    return matches(request, torneio_id, edit_mode='team_assignment')


@permission_required("torneio.create_partida")
def generate_bracket_knockout(request, torneio_id):
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    torneio.gera_partidas_chaveamento_eliminatorias(False)
    return HttpResponseRedirect(reverse("torneio:matches", args=(torneio_id, )))

@permission_required("torneio.definir_resultado_partida")
def edit_match_results(request, torneio_id):
    return matches(request, torneio_id, edit_mode='results')

@permission_required("torneio.definir_resultado_partida")
def set_match_result(request, torneio_id,  partida_num):
    if request.method != 'POST':
        return HttpResponseForbidden(_("Método não permitido"))
    torneio = get_object_or_404(Torneio, pk=torneio_id)
    partidas = Partida.objects.filter(torneio=torneio, numero=partida_num)
    if not partidas or partidas.count() != 1:
        return HttpResponseNotFound(_("Partida inexistente"))
    partida = partidas[0]
    if (not 'pontos_time1' in request.POST) or (not 'pontos_time2' in request.POST):
        return HttpResponseBadRequest(_('Faltam dados'))
    pontos1 = int(request.POST['pontos_time1'])
    pontos2 = int(request.POST['pontos_time2'])
    partida.resultado_time1 = pontos1
    partida.resultado_time2 = pontos2
    partida.vencedor = 1 if pontos1 > pontos2 else 2 if pontos2 > pontos1 else 0
    partida.save()
    
    return HttpResponseRedirect(reverse("torneio:matches", args=(torneio.id, )))
