# -*- coding: utf-8 -*-
import requests
import sys
import socket
import os
import datetime
from functools import reduce
import time
from random import randint
import sys
from art import tprint
DEBUG=False
import gc

def help_func():
    tprint("help")
    print("By Sepand Haghighi\n")
    print("python -m gitfollow")
    print("- run  (run gitfollow)\n")
    print("- test (run tests)\n")
    print("- help (help page)\n")
def zero_insert(input_string):
    '''
    This function get a string as input if input is one digit add a zero
    :param input_string: input digit az string
    :type input_string:str
    :return: modified output as str
    '''
    if len(input_string)==1:
        return "0"+input_string
    return input_string

def time_convert(input_string):
    '''
    This function convert input_string from uptime from sec to DD,HH,MM,SS Format
    :param input_string: input time string  in sec
    :type input_string:str
    :return: converted time as string
    '''
    input_sec=float(input_string)
    input_minute=input_sec//60
    input_sec=int(input_sec-input_minute*60)
    input_hour=input_minute//60
    input_minute=int(input_minute-input_hour*60)
    input_day=int(input_hour//24)
    input_hour=int(input_hour-input_day*24)
    return zero_insert(str(input_day))+" days, "+zero_insert(str(input_hour))+" hour, "+zero_insert(str(input_minute))+" minutes, "+zero_insert(str(input_sec))+" seconds"

def url_maker_following(Name,page_number):
    '''
    This function return github following page url
    :param Name: Username
    :param page_number: page nubmer of following page
    :type Name:str
    :type Page:int
    :return: github following url as string
    '''
    return "https://github.com/"+Name+"?page="+str(page_number)+"&tab=following"

def url_maker_repo(Name,page_number):
    '''
    This function return github repo page url
    :param Name: Username
    :param page_number: page nubmer of repos page
    :type Name:str
    :type Page:int
    :return: github repos url as string
    '''
    return "https://github.com/"+Name+"?page="+str(page_number)+"&tab=repositories"
def url_maker_follower(Name,page_number):
    '''
    This function return github follower page url
    :param Name: username
    :param page_number: page number of follower page
    :type Name:str
    :type page_number:int
    :return: github follower url as string
    '''
    return "https://github.com/" + Name + "?page=" + str(page_number) + "&tab=followers"
def url_maker_star(Name,page_number):
    '''
    This function return github stars page url
    :param Name: username
    :param page_number: page number of stars
    :type Name :str
    :type page_number:int
    :return: github star url as string
    '''
    return "https://github.com/"+Name+"?page="+str(page_number)+"&tab=stars"
def repo_extract(input_string,username):
    '''
    This function extract repo from raw_html
    :param input_string: raw input html
    :param user_name: user_name
    :type input_string:str
    :type user_name:str
    :return: repo_list as list
    '''
    try:
        user_list=[]
        index=0
        shift=len(username)+1
        while(index!=-1):
            index=input_string.find('src="/'+username,index+shift,len(input_string))
            length=input_string[index:].find('graphs/')
            star_repo=input_string[index+5:index+length]
            if star_repo.find("<svg")==-1 and len(star_repo)!=0:
                user_list.append(star_repo)
        return user_list
    except Exception as ex:
        pass

def star_extract(input_string):
    '''
    This function extract stared repo from raw_html
    :param input_string: raw input html
    :param follower_name: follower_name
    :type input_string:str
    :type follower_name:str
    :return: user_list as list
    '''
    user_list=[]
    index=0
    try:
        while(index!=-1):
            index=input_string.find('<a class="muted-link mr-3',index+33,len(input_string))
            length=input_string[index+33:].find('stargazers">\n')
            star_repo=input_string[index+34:index+33+length]
            if star_repo.find("<svg")==-1 and len(star_repo)!=0:
                user_list.append(star_repo)
        return user_list
    except Exception as ex:
        pass
def org_list_gen(name):
    url=url_maker_follower(name,1)
    raw_data=get_html(url)
    org_index = raw_data.find("col-9 float-left pl-2")
    org_data = raw_data[:org_index]
    index=0
    org_list=[]
    while (index != -1):
        index = org_data.find('alt="@', index + 6, len(org_data))
        length = org_data[index + 6:].find('"')
        org_name = org_data[index + 6:index + 6 + length]
        if org_name != name:
            org_list.append(org_name)
    return org_list[:-1]
def user_list_gen(input_string,follower_name):
    '''
    This function extract usernames from raw_html
    :param input_string: raw input html
    :param follower_name: follower_name
    :type input_string:str
    :type follower_name:str
    :return: user_list as list
    '''
    try:
        user_list = []
        index = 0
        org_index=input_string.find("col-9 float-left pl-2")
        repo_data=input_string[org_index:]
        while(index!=-1):
            index=repo_data.find('alt="@',index+6,len(input_string))
            length=repo_data[index+6:].find('"')
            user_name=repo_data[index+6:index+6+length]
            if user_name!=follower_name:
                user_list.append(user_name)
        return user_list[:-1]
    except Exception as ex:
        pass
def get_html(url):
    '''
    This function extract raw_html file
    :param url: url
    :type url:str
    :return: html data
    '''
    time.sleep(create_random_sleep())
    if internet()==True:
        new_session=requests.session()
        new_session.cookies.clear()
        raw_html=new_session.get(url)
        new_session.close()
        raw_data=raw_html.text
        if "Not Found" in raw_data:
            print("Invalid Github User")
            sys.exit()
        return raw_data
    else:
        print("Error In Internet")
        pass


def end_check(input_string):
    '''
    This function check end page
    :param input_string: raw html
    :type input_string:str
    :return: True or False
    '''
    if input_string.find("reached the end")!=-1:
        return True
    else:
        return False
def follower_list_gen(follower_name,page_number=0,counter=0):
    '''
    This function generate follower_list
    :param follower_name: username
    :type follower_name:str
    :return: username follower list
    '''
    try:
        follower_list = []
        while (True):
            page_number += 1
            follower_url = url_maker_follower(follower_name, page_number)
            follower_html = get_html(follower_url)
            if end_check(follower_html) == True:
                break
            temp_list = user_list_gen(follower_html,follower_name)
            follower_list.extend(temp_list)
        return follower_list
    except Exception as ex:
        if counter>3:
            sys.exit()
        error_log("Error In Page "+str(page_number)+" Follower Page")
        follower_list_gen(follower_name,page_number,counter+1)
def repo_list(username,page_number=0,counter=0):
    '''
    This function return stared_repo list
    :param username: username
    :type username:str
    :return: stared repo as list
    '''
    try:
        repo_list_temp=[]
        while (True):
            page_number += 1
            repo_url = url_maker_repo(username, page_number)
            repo_html = get_html(repo_url)
            temp_list = repo_extract(repo_html,username)
            if len(temp_list)==0:
                break
            repo_list_temp.extend(temp_list)
        return repo_list_temp
    except Exception as ex:
        if counter>3:
            sys.exit()
        error_log("Error In Page " + str(page_number) + " Repos Page")
        repo_list(username,page_number,counter+1)
def star_list(username,page_number=0,counter=0):
    '''
    This function return stared_repo list
    :param username: username
    :type username:str
    :return: stared repo as list
    '''
    try:
        star_list_temp=[]
        while (True):
            page_number += 1
            star_url = url_maker_star(username, page_number)
            star_html = get_html(star_url)
            temp_list = star_extract(star_html)
            if len(temp_list)==0:
                break
            star_list_temp.extend(temp_list)
        return star_list_temp
    except Exception as ex:
        if counter>3:
            sys.exit()
        error_log("Error In Page " + str(page_number) + " Stars Page")
        star_list(username,page_number,counter+1)

def following_list_gen(follower_name,page_number=0,counter=0):
    '''
    This function generate following list
    :param follower_name: username
    :type follower_name:str
    :return: username following list
    '''
    try:
        following_list = []
        while (True):
            page_number+=1
            following_url = url_maker_following(follower_name, page_number)
            following_html = get_html(following_url)
            if end_check(following_html) == True:
                break
            temp_list = user_list_gen(following_html,follower_name)
            following_list.extend(temp_list)
        return following_list
    except Exception as ex:
        if counter>3:
            sys.exit()
        error_log("Error In Page " + str(page_number) + " Following Page")
        following_list_gen(follower_name,page_number,counter+1)

def error_log(msg):
    """
    Create the errorlog of the app
    :param msg: error message
    :type msg:str
    """
    if "log" not in os.listdir():
        os.mkdir("log")
    file = open(reduce(os.path.join, [os.getcwd(), "log", "error_log.txt"]), "a")
    file.write(str(datetime.datetime.now()) + " --> " + str(msg) + "\n")
    file.close()

def internet(host="8.8.8.8", port=53, timeout=10):
    """
    Check Internet Connections.
    :param  host: the host that check connection to
    :param  port: port that check connection with
    :param  timeout: times that check the connnection
    :type host:str
    :type port:int
    :type timeout:int
    :return bool: True if Connection is Stable
    >>> internet() # if there is stable internet connection
    True
    >>> internet() # if there is no stable internet connection
    False
    """
    try:
        socket.setdefaulttimeout(timeout)
        socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
        return True
    except Exception as ex:
        return False

def create_random_sleep(index=1,min_time=1,max_time=3):
    '''
    This function generate sleep time with random processes
    :param index: index to determine first page  and messages(index = 0 is for first page)
    :param min_time: minimum time of sleep
    :param max_time: maximum time of sleep
    :type index:int
    :type min_time:int
    :type max_time:int
    :return: time of sleep as integer (a number between max and min)
    '''
    if index==0:
        time_sleep = 5
        if DEBUG==True:
            print("Wait "+str(time_sleep)+" sec for first search . . .")
    else:
        time_sleep = randint(min_time, max_time)
        if DEBUG==True:
            print("Wait "+str(time_sleep)+" sec for next search . . .")
    if DEBUG==True:
        print_line(70,"*")
    return time_sleep

def print_line(number=30,char="-"):
    '''
    This function print line in screen
    :param number: number of items in each line
    :param char: each char of line
    :return: None
    '''
    line=""
    for i in range(number):
        line=line+char
    print(line)


def list_maker(username,keyword=["1"]):
    '''
    This function create following and follower list
    :param username: username
    :type username:str
    :return: (list_1,list_2) as tuple
    '''
    try:
        print("Collecting Follower Information ...")
        print_line(70, "*")
        list_1 = follower_list_gen(username)
        file = open(username + "_follower.log", "w")
        print(str(len(list_1)) + " Followers --> " + username + "_follower.log")
        print_line(70, "*")
        file.write("\n".join(list_1))
        file.close()
        print('Collecting Following Information ...')
        print_line(70, "*")
        list_2 = following_list_gen(username)
        file = open(username + "_following.log", "w")
        print(str(len(list_2)) + " Following --> " + username + "_following.log")
        print_line(70, "*")
        file.write("\n".join(list_2))
        file.close()
        if "1" in keyword:
            print('Collecting Stars Information ...')
            print_line(70, "*")
            stars=star_list(username)
            file = open(username + "_stars.log", "w")
            print(str(len(stars)) + " Stars --> " + username + "_stars.log")
            print_line(70, "*")
            file.write("\n".join(stars))
            file.close()
        if "2" in keyword:
            print('Collecting Repos Information ...')
            print_line(70, "*")
            repos = repo_list(username)
            file = open(username + "_repos.log", "w")
            print(str(len(repos)) + " Repos --> " + username + "_repos.log")
            print_line(70, "*")
            file.write("\n".join(repos))
            file.close()
        if "3" in keyword:
            print('Collecting Organizations Information ...')
            print_line(70, "*")
            orgs = org_list_gen(username)
            file = open(username + "_orgs.log", "w")
            print(str(len(orgs)) + " Organizations --> " + username + "_orgs.log")
            print_line(70, "*")
            file.write("\n".join(orgs))
            file.close()
        return (sorted(list_1),sorted(list_2))
    except Exception as ex:
        error_log(str(ex))

def dif(list_1,list_2,username):
    '''
    This function generate dif files
    :param list_1:follower list
    :param list_2: following list
    :type list_1:list
    :type list_2:list
    :return: None
    '''
    try:
        file = open(username + "_NotFollower.log", "w")
        dif_list_1 = list(set(list_2) - set(list_1))
        print(str(len(dif_list_1)) + " Following - Not Follower --> " + username + "_NotFollower.log")
        print_line(70, "*")
        file.write("\n".join(dif_list_1))
        file.close()
        file = open(username + "_NotFollowing.log", "w")
        dif_list_2 = list(set(list_1) - set(list_2))
        print(str(len(dif_list_2)) + " Follower - Not Following --> " + username + "_NotFollowing.log")
        print_line(70, "*")
        file.write("\n".join(dif_list_2))
        file.close()
        return [sorted(dif_list_1),sorted(dif_list_2)]
    except Exception as ex:
        print("[Error] dif function faild")
def unfollow(username,password,id_list):
    for user in id_list:
        response=requests.delete("https://api.github.com/user/following/" + user, auth=(username, password))
        status_code=int(response.status_code)
        if status_code!=204:
            if status_code==401:
                print("[Error] Authentication Error")
                return False
            elif status_code==403:
                print("[Error] Maximum number of login attempts exceeded")
                sys.exit()
            else:
                print("[Error] in " + user + " unfollow!")
        else:
            print(user+" Unfollowed")
        time.sleep(3)
    return True
def follow(username,password,id_list):
    for user in id_list:
        response = requests.put("https://api.github.com/user/following/" + user, auth=(username, password))
        status_code = int(response.status_code)
        if status_code!=204:
            if status_code==401:
                print("[Error] Authentication Error")
                return False
            elif status_code==403:
                print("[Error] Maximum number of login attempts exceeded")
                sys.exit()
            else:
                print("[Error] in "+user+" follow!")
        else:
            print(user+" Followed")
        time.sleep(3)
    return True
def get_input(func_1=input,func_2=input):
    tprint("git\nfollow")
    username = func_1("Please Enter Your Github Username : ")
    keys=func_2("1- Collecting Stars Information\n2- Collecting Repos Information\n3- Collecting Organizations Information (Enter Number Seperated By , )")
    keys=keys.split(",")
    return [username,keys]
def run(func_1=input,func_2=input,func_3=input):
    password = ""
    auth=False
    time_1 = time.perf_counter()
    [username,keys]=get_input(func_1=func_1,func_2=func_2)
    (list_1, list_2) = list_maker(username,keys)
    dif_lists = dif(list_1, list_2, username)
    time_2 = time.perf_counter()
    dif_time = str(time_2 - time_1)
    print("Data Generated In " + time_convert(dif_time) + " sec")
    print("Log Files Are Ready --> " + os.getcwd())
    if len(dif_lists[0])>0:
        input_data = func_3("Unfollow Non-follower?Yes[y],No[n] ")
        if input_data.upper() == "Y":
            while(auth==False):
                password = func_2("Please Enter Password : ")
                print("Processing ... ")
                auth=unfollow(username, password, dif_lists[0])
    if len(dif_lists[1])>0:
        input_data = func_3("Follow Non-following?Yes[y],No[n] ")
        if input_data.upper() == "Y":
            if auth==True:
                print("Processing ... ")
                auth= follow(username, password, dif_lists[1])
            while(auth==False):
                password = func_2("Please Enter Password : ")
                print("Processing ... ")
                auth=follow(username, password, dif_lists[1])
    gc.collect()

def test_function_1(a):
    return 'sarminh'
def test_function_2(a):
    return "1,2,3"
def test_function_3(a):
    return "y"