# %%
PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'
import os, sys
sys.path.insert(0, PROJECT_PATH)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "leagues.settings")
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

from leagues import settings
settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'
# settings.DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
# settings.DATABASES['default']['HOST'] = '0.0.0.0'
# settings.DATABASES['default']['PORT'] = '5432'
# settings.DATABASES['default']['USER'] = 'postgres'
# settings.DATABASES['default']['PASSWORD'] = 'secret123'
# settings.DATABASES['default']['NAME'] = 'mypgsqldb'
# settings.DATABASES['default']['ATOMIC_REQUESTS'] = False
# settings.DATABASES['default']['AUTOCOMMIT'] = True
# settings.DATABASES['default']['CONN_MAX_AGE'] = 0
# settings.DATABASES['default']['CONN_HEALTH_CHECKS'] = False
# settings.DATABASES['default']['OPTIONS'] = {}

os.environ["XPRESSDIR"] = "/opt/xpressmp_8.4"
os.environ["XPRESS"] = "/opt/xpressmp_8.4/bin"
os.environ["LD_LIBRARY_PATH"] = os.environ["XPRESSDIR"] + "/lib"
os.environ["DYLD_LIBRARY_PATH"] = os.environ["XPRESSDIR"] + "/lib"
os.environ["SHLIB_PATH"] = os.environ["XPRESSDIR"] + "/lib"
os.environ["LIBPATH"] = os.environ["XPRESSDIR"] + "/lib"
os.environ["PYTHONPATH"] = os.environ["XPRESSDIR"] + "/lib"
os.environ["CLASSPATH"] = os.environ["XPRESSDIR"] + "/lib/xprs.jar"
os.environ["CLASSPATH"] = os.environ["XPRESSDIR"] + "/lib/xprb.jar" + os.pathsep + os.environ["CLASSPATH"]
os.environ["CLASSPATH"] = os.environ["XPRESSDIR"] + "/lib/xprm.jar" + os.pathsep + os.environ["CLASSPATH"]
os.environ["PATH"] = os.environ["XPRESSDIR"] + "/bin" + os.pathsep + os.environ["PATH"]


import django
django.setup()

from scheduler.models import *
from draws.models import *
import csv
from multiprocessing import Pool, cpu_count
import random
import time
import pulp
from pulp import lpSum, value, XPRESS, GUROBI, PULP_CBC_CMD
import gurobipy as gp
from gurobipy import GRB
import xpress as xp
# xp.controls.outputlog = 0



scenario = Scenario.objects.get(id=4)

# %%

teamObjects = Team.objects.filter(season=scenario.season,active=True).order_by('pot')
teams = teamObjects.values('id','country','pot','name')

getTeamById = {}
for t in teamObjects:
    getTeamById[t.id] = f"({t.pot}) {t.name}"

getCountryById = {}
for t in teamObjects:
    getCountryById[t.id] = t.country


countries = list(set(teamObjects.values_list('country', flat=True)))
teams_from_country = {
    c:[t for t in teams if t['country']==c] for c in countries 
}

pot = {}
for i in teamObjects.values_list('pot',flat=True).distinct():
    pot[i] = list(teams.filter(pot=i))

teams = list(teams)




# fixed_games = [(3940, 3894), (3933, 3940), (3940, 3937), (3765, 3940), (3940, 3895), (3700, 3940), (3940, 3696), (3925, 3940), (3702, 3935), (3894, 3702), (3702, 3926), (3937, 3702), (3702, 3700), (3758, 3702), (3702, 3699), (3757, 3702), (3932, 3762), (3732, 3932), (3932, 3938), (3761, 3932), (3932, 3730), (3701, 3932), (3932, 3870), (3853, 3932), (3764, 3933), (3935, 3764), (3764, 3936), (3898, 3764), (3764, 3756), (3928, 3764), (3764, 3925), (3904, 3764), (3762, 3732), (3762, 3761), (3930, 3762), (3762, 3701), (3760, 3762), (3762, 3697), (3698, 3762), (3732, 3765), (3926, 3732), (3732, 3928), (3730, 3732), (3732, 3698), (3870, 3732), (3894, 3930), (3938, 3894), (3894, 3758)]



def check_feasible_pulp(fixed_games):

    model = pulp.LpProblem(f"Draws", pulp.LpMinimize)

    x = {}
    for t1 in teams:
        for t2 in teams:
            if t1['country'] != t2['country']:
                x[t1['id'], t2['id']] = pulp.LpVariable('x_'+str(t1['id'])+'_'+str(t2['id']),lowBound=0, upBound=1, cat=pulp.LpInteger)


    # REQUIREMENTS
    for t in teams:
        for r in range(1,5):
            # model.addConstraint(xp.Sum(x[t['id'],t2['id']] for t2 in pot[r] if (t['id'],t2['id']) in x.keys()) == 1)
            # model.addConstraint(xp.Sum(x[t2['id'],t['id']] for t2 in pot[r] if (t2['id'],t['id']) in x.keys()) == 1)
            model += lpSum(x[t['id'],t2['id']] for t2 in pot[r] if (t['id'],t2['id']) in x.keys()) == 1
            model += lpSum(x[t2['id'],t['id']] for t2 in pot[r] if (t2['id'],t['id']) in x.keys()) == 1

        
        for c in countries:
            if c != t['country']:
                # model.addConstraint(xp.Sum(x[t['id'],t2['id']]+x[t2['id'],t['id']] for t2 in teams_from_country[c]) <= 3)
                model += lpSum(x[t['id'],t2['id']] for t2 in teams_from_country[c]) <= 2

    # FIXATIONS
    for (t1,t2) in fixed_games:
        # print("FIXING",t1,t2)
        # model.addConstraint(x[t1,t2] == 1)
        model += x[t1,t2] == 1

    for (t1,t2) in x.keys():
        model += x[t1,t2] + x[t2,t1] <= 1, f'directed_{t1}_{t2}'
        # model.addConstraint(x[t1,t2] + x[t2,t1] <= 1)

    model += lpSum(random.uniform(0,1)*x[key] for key in x.keys())
    tt =time.time()
    model.solve(XPRESS(msg=0,timeLimit=120,keepFiles=0))
    comp_time = time.time()-tt

    if model.status in [-1,-2]:
        return False, comp_time
    else:
        return True, comp_time
        



model = gp.Model('Draws')
model.Params.OutputFlag = 0

x = {}
for t1 in teams:
    for t2 in teams:
        if t1['country'] != t2['country']:
            # x[t1['id'], t2['id']] = xp.var(ub=1, vartype=xp.integer)
            x[t1['id'], t2['id']] = model.addVar(vtype=GRB.BINARY)

# REQUIREMENTS
for t in teams:
    for r in range(1,5):
        model.addConstr(gp.quicksum(x[t['id'],t2['id']] for t2 in pot[r] if (t['id'],t2['id']) in x.keys()) == 1)
        model.addConstr(gp.quicksum(x[t2['id'],t['id']] for t2 in pot[r] if (t2['id'],t['id']) in x.keys()) == 1)
    for c in countries:
        if c != t['country']:
            model.addConstr(gp.quicksum(x[t['id'],t2['id']]+x[t2['id'],t['id']] for t2 in teams_from_country[c]) <= 2)



for clash in Clash.objects.filter(draw__season=scenario.season):            
    for c1 in clash.countries.all():
        for c2 in clash.countries.all():
            if c1 != c2 and teams_from_country.get(c1.shortname) and teams_from_country.get(c2.shortname):
                model.addConstr(gp.quicksum(x[t['id'],t2['id']]+x[t2['id'],t['id']] for t in teams_from_country[c1.shortname] for t2 in teams_from_country[c2.shortname]) <= 0)
                print("CLASH",c1,c2)


for (t1,t2) in x.keys():
    model.addConstr(x[t1,t2] + x[t2,t1] <= 1)




def check_feasible_gurobi(fixed_games):
    
    # FIXATIONS
    for key in x.keys():
        if key in fixed_games:
            x[key].lb = 1
        else:
            x[key].lb = 0    
    
    model.update()

    # tt =time.time()
    # model.solve()
    model.optimize()
    # comp_time = time.time()-tt


    if model.Status == GRB.OPTIMAL:
        return True, 0
    else:
        # print("INFEASIBLE FOUND")
        return False, 0


def check_feasible(fixed_games):
    model = xp.problem(name='Draws', sense=xp.minimize)
    model.setControl ('outputlog', 1)

    x = {}
    for t1 in teams:
        for t2 in teams:
            if t1['country'] != t2['country']:
                x[t1['id'], t2['id']] = xp.var(ub=1, vartype=xp.integer)

    model.addVariable(x)

    # REQUIREMENTS
    for t in teams:
        for r in range(1,5):
            model.addConstraint(xp.Sum(x[t['id'],t2['id']] for t2 in pot[r] if (t['id'],t2['id']) in x.keys()) == 1)
            model.addConstraint(xp.Sum(x[t2['id'],t['id']] for t2 in pot[r] if (t2['id'],t['id']) in x.keys()) == 1)
        for c in countries:
            if c != t['country']:
                model.addConstraint(xp.Sum(x[t['id'],t2['id']]+x[t2['id'],t['id']] for t2 in teams_from_country[c]) <= 5)

    # FIXATIONS
    for (t1,t2) in fixed_games:
        # print("FIXING",t1,t2)
        model.addConstraint(x[t1,t2] == 1)

    for (t1,t2) in x.keys():
        model.addConstraint(x[t1,t2] + x[t2,t1] <= 1)

    
    tt =time.time()
    model.solve()
    comp_time = time.time()-tt


    if model.getProbStatus() != 6:
        # print("INFEASIBLE FOUND")
        return False, comp_time
    else:
        return True, comp_time
    



# model = xp.problem(name='Draws', sense=xp.minimize)
# model.setControl ('outputlog', 0)

# x = {}
# for t1 in teams:
#     for t2 in teams:
#         if t1['country'] != t2['country']:
#             x[t1['id'], t2['id']] = xp.var(ub=1, vartype=xp.integer)

# model.addVariable(x)

# # REQUIREMENTS
# for t in teams:
#     for r in range(1,5):
#         model.addConstraint(xp.Sum(x[t['id'],t2['id']] for t2 in pot[r] if (t['id'],t2['id']) in x.keys()) == 1)
#         model.addConstraint(xp.Sum(x[t2['id'],t['id']] for t2 in pot[r] if (t2['id'],t['id']) in x.keys()) == 1)
#     for c in countries:
#         if c != t['country']:
#             model.addConstraint(xp.Sum(x[t['id'],t2['id']]+x[t2['id'],t['id']] for t2 in teams_from_country[c]) <= 2)

# for (t1,t2) in x.keys():
#     model.addConstraint(x[t1,t2] + x[t2,t1] <= 1)

def check_feasible_fix(fixed_games):
    # FIXATIONS
    # for key in x.keys():
    #     if key in fixed_games:
    #         # x[key].lb = 1
    #         model.chgbounds([x[key]],['L'],[1])
    #     else:
    #         # x[key].lb = 0
    #         model.chgbounds([x[key]],['L'],[0])
    
    reset_bounds = [key for key in x.keys() if key not in fixed_games]

    model.chgbounds([x[key] for key in fixed_games],['L' for _ in range(len(fixed_games))],[1 for _ in range(len(fixed_games))])
    model.chgbounds([x[key] for key in reset_bounds],['L' for _ in range(len(reset_bounds))],[0 for _ in range(len(reset_bounds))])

    tt =time.time()
    model.solve()
    comp_time = time.time()-tt

    if model.getProbStatus() != 6:
        # print("INFEASIBLE FOUND")
        return False, comp_time
        return False
    else:
        return True, comp_time
        return True
    
    



import itertools
from itertools import permutations , product

def simulate_draws(filename,n):

    print("RUNNING ASYNC",filename)
    fixed_games=None
    for i in range(1, n):
        if i % 100 == 0:
            print("RUNNING ASYNC",filename,i)    
        # start_time = time.time()      
        # n_computations = 0  
        # check_time = 0
        # total_comp_time = 0


        possible_opps = {}
        for pos in range(8):
            p = pos//2+1
            teams_from_pot = list(teamObjects.filter(pot=p).values('id','country'))
            possible_opps[pos] = {
                t['id']: [t2['id'] for t2 in teams_from_pot if t2['country'] != t['country']] for t in teams
            }

        sol_opps = {
            (t['id'],p):None
            for t in teams for p in range(8)
        }

        sol_countries = {
            (t['id'],c):0
            for t in teams for c in countries if c != t['country']
        }

        fixed_games = []
        feasible = True

        for p in range(1,5): 
            currentPot = list(teamObjects.filter(pot=p).values_list('id', flat=True))

            while(feasible and currentPot):    
                new_team = currentPot.pop(random.randint(0,len(currentPot)-1))
                for current_pos in range(2*(p-1),8,2):
                    if sol_opps[new_team,current_pos] != None and sol_opps[new_team,current_pos+1] != None:
                        continue

                    possible_opps[current_pos][new_team] = [t for t in possible_opps[current_pos][new_team] if sol_countries[new_team,getCountryById[t]] < 2 and 
                                                            sol_countries[t,getCountryById[new_team]] < 2 and sol_opps[t,(2*p-1)] == None]
                    possible_opps[current_pos+1][new_team] = [t for t in possible_opps[current_pos+1][new_team] if sol_countries[new_team,getCountryById[t]] < 2 and 
                                                            sol_countries[t,getCountryById[new_team]] < 2 and sol_opps[t,(2*p-1)-1] == None]
                    
                    list_1 = [None]
                    list_2 = [None]
                    if sol_opps[new_team,current_pos] == None:
                        list_1 = possible_opps[current_pos][new_team]
                    if sol_opps[new_team,current_pos+1] == None:
                        list_2 = possible_opps[current_pos+1][new_team]

                    # unique_combinations = []
                    # for i in range(len(list_1)):
                    #     for j in range(len(list_2)):
                    #         unique_combinations.append((list_1[i], list_2[j]))
                    unique_combinations = list(itertools.product(list_1, list_2))
                    random.shuffle(unique_combinations)


                    no_solution_found = True
                    while (no_solution_found):
                        new_games = []
                        # if sol_opps[new_team,current_pos] == None:
                        #     new_opponent1 = random.choice(possible_opps[current_pos][new_team])
                        #     new_games.append((new_team,new_opponent1))

                        # if sol_opps[new_team,current_pos+1] == None:
                        #     new_opponent2 = random.choice(possible_opps[current_pos+1][new_team])
                        #     new_games.append((new_opponent2,new_team))
                        
                        new_opponent1,new_opponent2 = unique_combinations.pop()
                        print(new_opponent1,new_opponent2)

                        if new_opponent1:                      
                            new_games.append((new_team,new_opponent1))

                        if new_opponent2:                      
                            new_games.append((new_opponent2,new_team))

                        # n_computations += 1
                        # tt = time.time()
                        check, comp_time = check_feasible_gurobi(fixed_games+new_games)
                        # check_time += time.time()-tt
                        # total_comp_time += comp_time
                        if check:
                            no_solution_found = False
                            if sol_opps[new_team,current_pos] == None:
                                sol_opps[new_team,current_pos] = new_opponent1                    
                                sol_countries[new_team,getCountryById[new_opponent1]] += 1                             
                                sol_opps[new_opponent1,(2*p-1)] = new_team                 
                                sol_countries[new_opponent1,getCountryById[new_team]] += 1
                                if new_team in possible_opps[(2*p-1)-1][new_opponent1]:                                    
                                    possible_opps[(2*p-1)-1][new_opponent1].remove(new_team)
                            if sol_opps[new_team,current_pos+1] == None:
                                sol_opps[new_team,current_pos+1] = new_opponent2   
                                sol_countries[new_team,getCountryById[new_opponent2]] += 1     
                                sol_opps[new_opponent2,(2*p-1)-(1)] = new_team
                                sol_countries[new_opponent2,getCountryById[new_team]] += 1
                                if new_team in possible_opps[(2*p-1)][new_opponent2]:                                    
                                    possible_opps[(2*p-1)][new_opponent2].remove(new_team)
                            fixed_games += new_games



        # print("COMPUTATIONS",n_computations)
        # print("TIME\t",time.time()-start_time)
        # print("CHECK\t",check_time)
        # print("COMP\t",total_comp_time)



        with open(filename+'.csv', "a") as f:
            for t in teams:
                f.write(f"{i},{t['id']},{';'.join([str(sol_opps[t['id'],p]) for p in range(8)])}\n")





        # WRITE SOL '.html'
        sol = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
        sol += " \
        <style>     \
            table, th, td {     \
                    border: 1px solid black;     \
                    border-collapse: collapse;     \
                    text-align: center;     \
                    min-width:40px;     \
                    padding:3px;     \
                    margin: 3px;     \
                    font-family : Arial;\
            }     \
            h1 {\
                font-family : Arial;\
            }\
        \
            #etable td:nth-child(10),#etable th:nth-child(10) { border-right: 5px solid black; }\
            #etable td:nth-child(19),#etable th:nth-child(19) { border-right: 5px solid black; }\
            #etable td:nth-child(28),#etable th:nth-child(28) { border-right: 5px solid black; }\
            tr:nth-child(9) { border-bottom: 5px solid black; }\
            tr:nth-child(18) { border-bottom: 5px solid black; }\
            tr:nth-child(27) { border-bottom: 5px solid black; }\
        </style> \
        "
        sol += "<table id='etable' style='border:5px solid black'>\n"
        sol += "<thead>\n"
        sol += "<tr>"
        sol += "<th></th>"
        sol += "<th colspan=9 style='border-right: 5px solid black;'>Pot A</th>"
        sol += "<th colspan=9 style='border-right: 5px solid black;'>Pot B</th>"
        sol += "<th colspan=9 style='border-right: 5px solid black;'>Pot C</th>"
        sol += "<th colspan=9>Pot D</th>"
        sol += "</tr>"
        sol += "<tr>\n"
        sol += f"<th></th>\n"
        for t in teams:
            sol += f"<th>{getTeamById[t['id']]}</th>\n"
        sol += "</tr>\n"
        sol += "</thead>\n"
        sol += "<tbody>\n"
        for t1 in teams:
            sol += "<tr>\n"
            sol += f"<td>{getTeamById[t1['id']]}</td>"
            for t2 in teams:
                if (t1['id'],t2['id']) in fixed_games:
                    sol += f"<td>{t2['country']}</td>"
                elif (t2['id'],t1['id']) in fixed_games:
                    sol += f"<td>@{t2['country']}</td>"
                else:
                    sol += f"<td></td>"
            sol += "</tr>\n"
        sol += "</tbody>\n"
        sol += "</table>\n"

        with open(f'debug.html', 'w') as f:
            f.write(sol)


# n = sys.maxsize

# pool = Pool()
# result = {}
# answer = {}
# n_threads = cpu_count()
# # n_threads = 1

# for cpu in range(n_threads):
#     result[cpu] = pool.apply_async(simulate_draws, args=(f'thread_{cpu}_pot_by_pot', n,))

# for cpu in range(n_threads):
#     answer[cpu] = result[cpu].get()


# %%
n = 2
simulate_draws('test', n)











# %%
