#pylint: disable=C,R,W0212,W1202,W1203

from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import AuthorizedSession
from google.oauth2.credentials import Credentials
import json
import os.path
import logging
import datetime
from time import sleep
import io
from PIL import Image, ImageDraw

CLIENT_ID_FILE = ".gphoto_oauth.json"
TOKEN_FILE = ".gphoto_token"

class GooglePhotoException(Exception):
    def __init__(self, errCode, errMsg, resp):
        super().__init__(errCode, errMsg)
        self.errCode = errCode
        self.errMsg = errMsg
        self.resp = resp

    def __repr__(self):
        return "GooglePhotoException " + repr(self.errCode) + ": " + repr(self.errMsg)

class GooglePhoto:

    SCOPES = ['https://www.googleapis.com/auth/photoslibrary',
              'https://www.googleapis.com/auth/photoslibrary.sharing']

    ALLOWED_EXTENSIONS = ('BMP', 'GIF', 'HEIC', 'ICO', 'JPG', 'PNG', 'TIFF', 'WEBP', 'RAW', '3GP', '3G2', 'ASF', 'AVI', 'DIVX', 'M2T', 'M2TS', 'M4V', 'MKV', 'MMV', 'MOD', 'MOV', 'MP4', 'MPG', 'MTS', 'TOD', 'WMV')

    def __init__(self):
        self.clientIdFile = CLIENT_ID_FILE
        self.tokenFile = TOKEN_FILE
        self.__getAuthorizedSession()

    def __startAuthorizationFlow(self):
        flow = InstalledAppFlow.from_client_secrets_file(
            self.clientIdFile,
            scopes = self.SCOPES)

        credentials = flow.run_local_server(host='localhost',
                                            port=8081,
                                            authorization_prompt_message="",
                                            success_message='The auth flow is complete; you may close this window.',
                                            open_browser=True)

        return credentials

    def __getAuthorizedSession(self):

        cred = None
        try:
            cred = Credentials.from_authorized_user_file(self.tokenFile, self.SCOPES)
        except OSError as err:
            logging.debug("Error opening auth token file - {0}".format(err))
        except ValueError:
            logging.debug("Error loading auth tokens - Incorrect format")

        if not cred:
            cred = self.__startAuthorizationFlow()

            try:
                cred_dict = {
                    'token': cred.token,
                    'refresh_token': cred.refresh_token,
                    'id_token': cred.id_token,
                    'scopes': cred.scopes,
                    'token_uri': cred.token_uri,
                    'client_id': cred.client_id,
                    'client_secret': cred.client_secret
                }
                with open(self.tokenFile, 'w') as fp:
                    json.dump(cred_dict, fp)
            except OSError as err:
                logging.debug("Could not save auth tokens - {0}".format(err))

        self.session = AuthorizedSession(cred)

    def get(self, url, **kwargs):
        return self.__request(url, get=True, **kwargs)

    def post(self, url, **kwargs):
        return self.__request(url, get=False, **kwargs)

    def __request(self, url, get = True, jsonResponse = True, **kwargs):

        for i in range(5):
            logging.debug(f"Sending {'GET' if get else 'POST'} request: {url}")
            for key, value in kwargs.items():
                if key == "data":
                    continue
                logging.debug(f"    {key}: {value}")

            if get:
                resp = self.session.get(url, **kwargs)
            else:
                resp = self.session.post(url, **kwargs)

            logging.debug(f"Server response: {resp}")

            if resp.status_code == 200:
                break

            if resp.status_code == 429:
                logging.info("Quota exceeded, sleeping a bit")
                sleep(30)
            elif 500 <= resp.status_code < 600:
                logging.info("Something 5xx went wrong, sleping a bit")
                sleep(2 ** i)
            else:
                raise GooglePhotoException(resp.status_code, resp.text, resp)
        else:
            raise GooglePhotoException(resp.status_code, resp.text, resp)

        if jsonResponse:
            return resp.json()
        return resp


    def getMediaItems(self, albumId, pageSize = None):

        params = {}
        params['albumId'] = albumId
        if pageSize:
            params['pageSize'] = pageSize

        while True:

            resp = self.post('https://photoslibrary.googleapis.com/v1/mediaItems:search', params=params)

            if 'mediaItems' in resp:

                for mi in resp["mediaItems"]:
                    yield mi

                if 'nextPageToken' in resp:
                    params["pageToken"] = resp["nextPageToken"]
                else:
                    return

            else:
                return

def main():

    logging.basicConfig(level=logging.INFO)

    api = GooglePhoto()

    albumTitle = f"TestAlbum{datetime.datetime.now().strftime('%d_%m_%Y-%H:%M:%S')}"
    print(albumTitle)
    albumId = api.post('https://photoslibrary.googleapis.com/v1/albums', json={"album":{"title": albumTitle}})["id"]
    print(albumId)
 
    for i in range(100, 0, -1):
        img = Image.new('RGB', (128, 128), color = (73, 109, 137))
        d = ImageDraw.Draw(img)
        d.text((10,10), f"{i}", fill=(255, 255, 0))
        with io.BytesIO() as fp:
            img.save(fp, format="jpeg")
            fp.seek(0)
            photo_bytes = fp.read()

            print (f"Uploading item {i:3.0f}", end="\r")

            headers = {}
            headers["Content-type"] = "application/octet-stream"
            headers["X-Goog-Upload-Protocol"] = "raw"
            headers["X-Goog-Upload-Content-Type"] = "image/jpeg"
        
            upload_token = api.post('https://photoslibrary.googleapis.com/v1/uploads', data=photo_bytes, jsonResponse=False)

        create_body = json.dumps({
                        "albumId":albumId,
                        "newMediaItems":[
                            {"description":"",
                            "simpleMediaItem":
                                {"fileName": f"{i}.jpg",
                                "uploadToken":upload_token.content.decode()}}]}, indent=4)

        api.post('https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate', data=create_body)

    print ("                   ", end="\r")

    def printState(msg):
        print(msg)
        a = api.get(f'https://photoslibrary.googleapis.com/v1/albums/{albumId}')
        print(f"MediaCount: {a['mediaItemsCount']}")
        print(f"Search result: {len(list(api.getMediaItems(albumId)))}")
        print(f"Search result[pageSize=100]: {len(list(api.getMediaItems(albumId, 100)))}")
        print(f"Search result[pageSize=90]: {len(list(api.getMediaItems(albumId, 90)))}")
        print(f"Search result[pageSize=10]: {len(list(api.getMediaItems(albumId, 10)))}")

    # Right after uploading images, mediaItemsCount 
    # and actual returned mediaItem list still consistent
    printState("After upload")

    print(f"Open {albumTitle} in Google Photos and change ordering to 'Recently added'.")
    input("Press Enter to continue...")

    # After changing order in Google Photos still consistent
    printState("After changing order on Google Photos")

    mediaItems = sorted([mi["id"] for mi in api.getMediaItems(albumId)])
    chunkSize = 25

    for i in range(0, len(mediaItems), chunkSize):
        chunk = mediaItems[i:i + chunkSize]
        request = { "mediaItemIds": chunk}
        print(f"Removing and adding {len(chunk)} items")
        api.post(f"https://photoslibrary.googleapis.com/v1/albums/{albumId}:batchRemoveMediaItems", json=request)
        api.post(f"https://photoslibrary.googleapis.com/v1/albums/{albumId}:batchAddMediaItems", json=request)

    # After removing/adding the whole list mediaItemsCount still correct,
    # Google Photos shows complete album,
    # but returned mediaItems list is wrong
    printState("After Remove+add")

if __name__ == '__main__':
    main()
