#!/usr/bin/env python3
#
# Copyright (c) 2024-2025 ABXR Labs, Inc.
# Released under the MIT License. See LICENSE file for details.
#

from tqdm import tqdm

from enum import Enum

from abxr.api_service import ApiService
from abxr.multipart import MultipartFileS3
from abxr.formats import DataOutputFormats
from abxr.output import print_formatted

class Commands(Enum):
    VERSIONS_LIST = "list"
    UPLOAD = "upload"
    RELEASE_CHANNELS_LIST = "release_channels"
    RELEASE_CHANNEL_DETAILS = "release_channel_details"
    APP_COMPATIBILITIES = "app_compatibilities"
    APP_COMPATIBILITY_DETAILS = "app_compatibility_details"


class SystemAppsService(ApiService):
    """System-app endpoints under /api/internal/.

    Note: the internal API returns UPPERCASE status enum names (AVAILABLE,
    UPLOADED, ERROR), not the lowercase v3 values used by the public API.
    Anything that compares against status strings here must match accordingly.
    """
    MAX_PARTS_PER_REQUEST = 4

    def __init__(self, base_url, token):
        super().__init__(base_url, token)
        self._internal_base = f'{self._base_origin}/api/internal'

    def _url(self, *segments):
        """Override: internal endpoints are non-versioned."""
        path = '/'.join(str(s).strip('/') for s in segments)
        return f'{self._internal_base}/{path}'

    def _initiate_upload(self, app_type, file_name, app_compatibility_id, version_name=None, version_code=None):
        url = self._url('apps', app_type, 'versions')

        data = {'filename': file_name,
                'appCompatibilityId': app_compatibility_id
                }

        if version_name:
            data['versionName'] = version_name

        if version_code:
            data['versionCode'] = version_code

        response = self.client.post(url, json=data, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def _presigned_url(self, app_type, version_id, upload_id, key, part_numbers):
        url = self._url('apps', app_type, 'versions', version_id, 'pre-sign')
        data = {'key': key,
                'uploadId': upload_id,
                'partNumbers': part_numbers
                }

        response = self.client.post(url, json=data, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def _complete_upload(self, app_type, version_id, upload_id, key, parts, version_name, release_notes):
        url = self._url('apps', app_type, 'versions', version_id, 'complete')
        data = {'key': key,
                'uploadId': upload_id,
                'parts': parts,
                'versionName': version_name,
                'releaseNotes': release_notes
                }

        response = self.client.post(url, json=data, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def upload_file(self, app_type, file_path, app_compatibility_name, version_number, version_code, release_notes, silent):
        app_compatibilities = self.get_all_app_compatibilities_for_app(app_type)
        app_compatibility_id = next((item['id'] for item in app_compatibilities if item['name'] == app_compatibility_name), None)
        if not app_compatibility_id:
            raise ValueError(f"App compatibility '{app_compatibility_name}' not found for app_type '{app_type}'.")

        file = MultipartFileS3(file_path)

        response = self._initiate_upload(app_type, file.file_name, app_compatibility_id, version_number, version_code)

        upload_id = response['uploadId']
        key = response['key']
        version_id = response['versionId']

        part_numbers = list(range(1, file.get_part_numbers() + 1))

        uploaded_parts = []

        with tqdm(total=file.get_size(), unit='B', unit_scale=True, desc=f'Uploading {file.file_name}', disable=silent) as pbar:
            for i in range(0, len(part_numbers), self.MAX_PARTS_PER_REQUEST):
                part_numbers_slice = part_numbers[i:i + self.MAX_PARTS_PER_REQUEST]

                presigned_url_response = self._presigned_url(app_type, version_id, upload_id, key, part_numbers_slice)

                for item in presigned_url_response:
                    part_number = item['partNumber']
                    presigned_url = item['presignedUrl']

                    part = file.get_part(part_number)
                    response = self.client.put(presigned_url, data=part)
                    response.raise_for_status()

                    uploaded_parts += [{'partNumber': part_number, 'eTag': response.headers['ETag']}]
                    pbar.update(len(part))

            complete_response = self._complete_upload(app_type, version_id, upload_id, key, uploaded_parts, version_number, release_notes)
            return complete_response

    def get_all_release_channels_for_app(self, app_type):
        url = self._url('apps', app_type, 'release-channels') + '?per_page=20'
        return self._get_all_pages(url)

    def get_release_channel_detail(self, app_type, release_channel_id):
        url = self._url('apps', app_type, 'release-channels', release_channel_id)

        response = self.client.get(url, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def get_all_app_compatibilities_for_app(self, app_type):
        url = self._url('apps', app_type, 'app-compatibilities') + '?per_page=20'
        return self._get_all_pages(url)

    def get_app_compatibility_detail(self, app_type, app_compatibility_id):
        url = self._url('apps', app_type, 'app-compatibilities', app_compatibility_id)

        response = self.client.get(url, headers=self.headers)
        response.raise_for_status()

        return response.json()

    def get_all_app_versions_by_type(self, app_type):
        url = self._url('apps', app_type, 'versions') + '?per_page=20'
        return self._get_all_pages(url)


class CommandHandler:
    def __init__(self, args):
        self.args = args
        self.service = SystemAppsService(self.args.url, self.args.token)

    def run(self):
        if self.args.system_apps_command == Commands.VERSIONS_LIST.value:
            app_versions = self.service.get_all_app_versions_by_type(self.args.app_type)
            print_formatted(self.args.format, app_versions)

        elif self.args.system_apps_command == Commands.RELEASE_CHANNELS_LIST.value:
            release_channels = self.service.get_all_release_channels_for_app(self.args.app_type)
            print_formatted(self.args.format, release_channels)

        elif self.args.system_apps_command == Commands.RELEASE_CHANNEL_DETAILS.value:
            release_channel_detail = self.service.get_release_channel_detail(self.args.app_type, self.args.release_channel_id)
            print_formatted(self.args.format, release_channel_detail)

        elif self.args.system_apps_command == Commands.APP_COMPATIBILITIES.value:
            app_compatibilities = self.service.get_all_app_compatibilities_for_app(self.args.app_type)
            print_formatted(self.args.format, app_compatibilities)

        elif self.args.system_apps_command == Commands.APP_COMPATIBILITY_DETAILS.value:
            app_compatibility_detail = self.service.get_app_compatibility_detail(self.args.app_type, self.args.app_compatibility_id)
            print_formatted(self.args.format, app_compatibility_detail)

        elif self.args.system_apps_command == Commands.UPLOAD.value:
            app_version = self.service.upload_file(self.args.app_type, self.args.filename, self.args.app_compatibility_name, self.args.version_number, self.args.version_code, self.args.notes, self.args.silent)
            print_formatted(self.args.format, app_version)
