from concurrent.futures import ThreadPoolExecutor
from BunnyCDN.Storage import Storage
from instagrapi import Client
import requests
import config
import json
import os
from PIL import Image
import cv2
import getpass
import time
import hashlib

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"}
proxies={
                "http": "http://yehyuxsl-rotate:4tl5bvrwkz5e@p.webshare.io:80/",
                "https": "http://yehyuxsl-rotate:4tl5bvrwkz5e@p.webshare.io:80/"
        }
def login():
    client = Client()

    try:
        client.load_settings("session_data.json")
    except (FileNotFoundError, json.JSONDecodeError):
        username = input("Enter your Instagram username: ")
        password = getpass.getpass("Enter your Instagram password: ")
        auth = input("Enter your 2FA code (leave blank if not enabled): ")
        if auth:
            client.login(username=username, password=password, verification_code=auth)
        else:
            client.login(username, password)
        client.dump_settings("session_data.json")

    return client

def get_media_details(media_item):
    mediaTypes = {1: 'image', 2: 'video', 8: 'album'}

    try:taken_at = media_item.taken_at
    except:taken_at = None
    try:post_type = media_item.product_type
    except:post_type = None

    mediaInfo = {'taken_at': taken_at, 'post_type' : post_type, 'media_type': mediaTypes[media_item.media_type]}

    if media_item.media_type == 1:  # Image
        mediaInfo['media_id'] = int(media_item.pk)
        mediaInfo['media_url'] = media_item.thumbnail_url
        mediaInfo['filename'] = f"{media_item.pk}.jpg"
    elif media_item.media_type == 2:  # Video
        mediaInfo['media_id'] = int(media_item.pk)
        mediaInfo['media_url'] = media_item.video_url
        try:mediaInfo['duration'] = media_item.video_duration
        except:mediaInfo['duration'] = 0
        mediaInfo['filename'] = f"{media_item.pk}.mp4"
    else:
        print(f"Unsupported media type with ID {media_item.pk}")
        return None
    
    return mediaInfo

def download_media(mediaInfo, save_dir, attempts=5):
    try:
        save_path = os.path.join(save_dir, mediaInfo['filename'])
        if not os.path.exists(os.path.dirname(save_path)):
            os.makedirs(os.path.dirname(save_path))

        response = requests.get(mediaInfo['media_url'], stream=True, proxies=proxies)
        response.raise_for_status()

        with open(save_path, 'wb') as out_file:
            for chunk in response.iter_content(chunk_size=8192):
                out_file.write(chunk)
                
        print(f"Downloaded {save_path}")

        if mediaInfo['media_type'] == 'image':
            with Image.open(save_path) as img:
                mediaInfo['width'], mediaInfo['height'] = img.size
        else:
            mediaInfo['width'], mediaInfo['height'] = get_video_dimensions(save_path)

        server_path = os.path.join('users', save_dir, mediaInfo['filename'])
        upload_to_storage(save_path, server_path)

        mediaInfo['server_url'] = f"https://storysave.b-cdn.net/{server_path}"
        add_media_to_db(mediaInfo)
        os.remove(save_path)
    except Exception as e:
        if attempts > 0:
            print(f"Error when processing {mediaInfo['media_url']}. Error: {e}. Retrying...")
            download_media(mediaInfo['media_url'], save_dir, mediaInfo['filename'], attempts-1)
        else:
            print(f"Unexpected error when processing {mediaInfo['media_url']}. Error: {e}")

def upload_to_storage(local_path, server_path):
    try:
        obj_storage = Storage('345697f9-d9aa-4a6b-a5ec8bffc16d-ceaf-453e', 'storysave')
        obj_storage.PutFile(local_path, server_path)
        print(f"Uploaded {local_path} to https://storysave.b-cdn.net/{server_path}")
    except Exception as e:
        print(f"Failed to upload {local_path} to {server_path}. Error: {e}")

def add_media_to_db(mediaInfo):
    media_id = mediaInfo['media_id']
    user_id = mediaInfo['user_id']
    username = mediaInfo['username']
    date = mediaInfo['taken_at']
    media_type = mediaInfo['media_type']
    post_type = mediaInfo['post_type']
    duration = mediaInfo.get('duration', 0)
    media_url = mediaInfo['server_url']
    width = mediaInfo['width']
    height = mediaInfo['height']

    try:
        db, cursor = config.gen_connection()

        query = """
        INSERT INTO media (user_id, username, date, media_type, post_type, media_url, duration, width, height, media_id)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        """
        cursor.execute(query, (user_id, username, date, media_type, post_type, media_url, duration, width, height, media_id))
        db.commit()
        print(f"Added media for {username} to the database.")
    except Exception as e:
        print(f"Failed to add media for {username} to the database. Error: {e}")

def insert_highlight_items(media_ids, highlight_id, title, user_id):
    try:
        db, cursor = config.gen_connection()

        query = "INSERT IGNORE INTO highlights (media_id, highlight_id, title, user_id) VALUES (%s, %s, %s, %s)"

        values = [(media_id, highlight_id, title, user_id) for media_id in media_ids]
        cursor.executemany(query, values)
        db.commit()
        if cursor.rowcount > 0:
            print(f"Added {cursor.rowcount} highlight items to the database.")
    except Exception as e:
        print(f"Failed to add highlight items to the database. Error: {e}")


def get_video_dimensions(video_path):
    cap = cv2.VideoCapture(video_path)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    cap.release()
    return width, height


if __name__ == '__main__':
    client = login()
    client.set_proxy(proxies['https'])
    
    db, cursor = config.gen_connection()

    cursor.execute("SELECT instagram_username, instagram_user_id, last_checked FROM following ORDER BY id DESC;")
    following = cursor.fetchall()

    cursor.execute("SELECT media_id FROM media;")
    existing_files = [media[0] for media in cursor.fetchall()]

    with ThreadPoolExecutor(max_workers=10) as executor:
        for user in following:
            try:
                firstImport = False
                username, user_id, lastchecked = user

                lastchecked = int(lastchecked.timestamp()) if lastchecked else None
                lastchecked = time.time() - lastchecked

                if not user_id:
                    firstImport = True
                    user_id = client.user_id_from_username(username)
                    cursor.execute("UPDATE following SET instagram_user_id = %s WHERE instagram_username = %s;", (user_id, username))
                    db.commit()
                    print(f"Updated user ID for {username} to {user_id}")

                profile_pic = client.user_info(user_id).profile_pic_url_hd
                download_media({'media_url': profile_pic, 'filename': f"{user_id}.jpg"}, os.path.join('profile_pics', username))
                
                print(f"[{username}]\nChecking: Stories")

                if lastchecked > 3600:
                    stories = client.user_stories(user_id)
                else:
                    stories = []
                    
                if firstImport:
                    highlights = client.user_highlights(user_id) # API request
                    for highlight in highlights:
                        try:
                            highlight_items = client.highlight_info_v1(highlight.pk).items # API request
                        except:
                            print(f"Failed to get highlight items for {highlight.pk}")
                            time.sleep(5)

                        media_ids = [item.pk for item in highlight_items]
                        executor.submit(insert_highlight_items, media_ids, highlight.pk, highlight.title, user_id)
                        stories.extend(highlight_items)

                newStoryCount = 0
                for story in stories:
                    mediaInfo = get_media_details(story)
                    if mediaInfo['media_id'] in existing_files: 
                        continue
                    
                    newStoryCount += 1
                    mediaInfo['user_id'] = user_id
                    mediaInfo['username'] = username
                    mediaInfo['post_type'] = 'story'
                    if mediaInfo['media_url'] and mediaInfo['filename']:
                        filePath = os.path.join('media', 'stories', username)
                        download_media(mediaInfo, filePath, mediaInfo['filename'])
                

                print("Checking: Posts")

                if lastchecked > 3600:
                    medias = client.user_medias(user_id, 9) # API request
                else:
                    medias = []
                    
                posts = []
                for post in medias:
                    if post.media_type == 8:
                        for item in post.resources:
                            posts.append(item)
                        continue
                    posts.append(post)

                newPostsCount = 0
                for post in posts:
                    mediaInfo = get_media_details(post)
                    if mediaInfo['media_id'] in existing_files:
                        continue

                    newPostsCount += 1
                    mediaInfo['user_id'] = user_id
                    mediaInfo['username'] = username
                    mediaInfo['post_type'] = 'post'
                    if mediaInfo['media_url'] and mediaInfo['filename']:
                        filePath = os.path.join('media', 'posts', username)
                        download_media(mediaInfo, filePath, mediaInfo['filename'])

                if newStoryCount > 0 or newPostsCount > 0:
                    cursor.execute("UPDATE following SET last_checked = NOW() WHERE instagram_username = %s;", (username,))
                    db.commit()

                print(f"New stories: {newStoryCount}\tNew Posts: {newPostsCount}")
                print("=====================================")

                time.sleep(5)
            except:
                print(f"Failed to get stories for {username}")
                time.sleep(5)