from _socket import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import random
from socketserver import BaseServer
import urllib.parse
import hashlib
import json


class AiDummyRequestHandler(BaseHTTPRequestHandler):

    @classmethod
    def to_sha1(cls, input: str) -> str:
        sha1 = hashlib.sha1()
        sha1.update(input.encode('utf-8'))
        return sha1.hexdigest()

    jobs: dict = {}

    def _insert_new_job(self, service: str, data: str) -> str:
        if not service in self.jobs:
            self.jobs[service] = {}

        next_id = len(self.jobs[service]) + 1
        next_id_hash = self.to_sha1(service + str(next_id))

        self.jobs[service][next_id_hash] = data

        self._save_jobs()

        return next_id_hash

    def _jobs_file(self) -> str:
        return os.path.abspath(os.path.join(os.path.dirname(__file__), 'jobs.json'))

    def _save_jobs(self) -> None:
        with open(self._jobs_file(), 'w') as f:
            json.dump(self.jobs, f, indent=4)

    def _load_jobs(self) -> None:
        try:
            with open(self._jobs_file(), 'r') as f:
                self.jobs = json.load(f)
        except:
            self.jobs = {}

    random_tags: list = []

    def _tags_file(self) -> str:
        return os.path.abspath(
            os.path.join(os.path.dirname(__file__), 'random_tags_from_chat_gpt.txt')
        )

    def _load_random_tags(self) -> str:
        try:
            with open(self._tags_file(), 'r') as f:
                for line in f:
                    line = line.strip()
                    if line == '':
                        continue
                    if line in self.random_tags:
                        continue
                    self.random_tags.append(line)
        except:
            self.random_tags = []

    # -----------------------------------------------------------------

    def _set_headers(self, status_code: int = 200) -> None:
        self.send_response(status_code)
        self.send_header('Content-type', 'application/json')
        self.end_headers()

    def _response(self, data: dict, status_code: int = 200) -> None:
        self._set_headers(status_code)
        self.wfile.write(json.dumps(data, indent=4).encode('utf-8'))

    def _response_endpoints(self) -> None:
        self._response(
            data={
                'GET': {
                    'cloudsight': {
                        '/images/<token>': 'get status of current job',
                    },
                    'deepva': {
                        '/jobs/<job_id>': 'get status of current job',
                    },
                    'imagga': {
                        '/tags': 'get status of current job',
                        'parameters': {
                            'image_upload_id': 'job id (upload_id)',
                        },
                    },
                },
                'POST': {
                    'cloudsight': {
                        '/images': 'post image, return new job with token',
                    },
                    'deepva': {
                        '/jobs': 'post image, return new job with job_id',
                    },
                    'imagga': {
                        '/uploads': 'post image, return new job with upload_id',
                    },
                },
            }
        )

    def _error_invalid_json(self) -> None:
        self._response(data={'error': 'Invalid JSON'}, status_code=400)

    def _error_not_found(self) -> None:
        self._response(data={'error': 'Not Found'}, status_code=404)

    # -----------------------------------------------------------------

    def _get_job_id(self, path: str) -> str:
        job_id = path.split('/')
        if len(job_id) != 4:
            return None

        job_id = job_id[3]
        if len(job_id) == 0:
            return None

        return job_id

    def _get_url_parameters(self, path: str) -> dict:
        query = urllib.parse.urlparse(path).query
        return urllib.parse.parse_qs(query)

    # -----------------------------------------------------------------

    def do_GET(self):

        if self.path in ['/', '/endpoints']:
            self._response_endpoints()
            return

        self._load_jobs()

        if self.path == '/jobs':
            self._response(data=self.jobs)
            return

        # -----------------------------------------------------------------
        # endpoints for Cloudsight
        # - get status of current job: '.../images/<job_id>'

        if self.path.startswith('/cloudsight/images/'):
            data = self.jobs.get('cloudsight')
            if not isinstance(data, dict):
                self._error_not_found()
                return

            job_id = self._get_job_id(self.path)
            if job_id is None:
                self._error_not_found()
                return

            data = data.get(job_id)
            if not isinstance(data, dict):
                self._error_not_found()
                return

            sorted_tags = [(tag, data[tag]) for tag in data]

            if len(sorted_tags) == 0:
                self._response(
                    {
                        'status': 'skipped',
                        'reason': 'unsure',
                    }
                )
                return

            # order descending by confidence
            sorted_tags.sort(key=lambda t: t[1], reverse=True)

            # subject + structured data
            i = 0
            result = {
                'status': 'completed',
                'name': sorted_tags[i][0],
                'structured_output': {},
            }

            i += 1
            if i < len(sorted_tags):
                result['categories'] = [sorted_tags[i][0]]
            i += 1
            if i < len(sorted_tags):
                result['similar_objects'] = [sorted_tags[i][0]]
            i += 1
            if i < len(sorted_tags):
                result['structured_output']['gender'] = [sorted_tags[i][0]]
            i += 1
            if i < len(sorted_tags):
                result['structured_output']['quantity'] = [sorted_tags[i][0]]
            i += 1
            if i < len(sorted_tags):
                result['structured_output']['material'] = [sorted_tags[i][0]]
            i += 1
            if i < len(sorted_tags):
                result['structured_output']['color'] = [sorted_tags[i][0]]

            self._response(result)
            return

        # -----------------------------------------------------------------
        # endpoints for DeepVA
        # get status of current job: '.../jobs/<job_id>'
        # https://docs.deepva.com/core-resources/job/

        if self.path.startswith('/deepva/jobs/'):
            data = self.jobs.get('deepva')
            if not isinstance(data, dict):
                self._error_not_found()
                return

            job_id = self._get_job_id(self.path)
            if job_id is None:
                self._error_not_found()
                return

            data = data.get(job_id)
            if not isinstance(data, dict):
                self._error_not_found()
                return

            items = [
                {
                    'type': 'object',
                    'label': tag,
                    'module': 'object_scene_recognition',
                }
                for tag in data
            ]

            if len(items) == 0:
                self._response(
                    {
                        'id': job_id,
                        'state': 'failed',
                        'progress': 0.0,
                        'result': {},
                    }
                )
                return

            self._response(
                {
                    'id': job_id,
                    'state': 'completed',
                    'progress': 1.0,
                    'result': {
                        'summary': [
                            {
                                'items': items,
                            }
                        ],
                    },
                }
            )
            return

        # -----------------------------------------------------------------
        # endpoints for Imagga
        # get status of current job: '.../tags'
        # https://docs.imagga.com/#tags

        if self.path.startswith('/imagga/tags?') or self.path.startswith(
            '/imagga/tags/?'
        ):
            data = self.jobs.get('imagga')
            if not isinstance(data, dict):
                self._error_not_found()
                return

            params = self._get_url_parameters(self.path)
            upload_ids = params.get('image_upload_id')
            upload_id = None
            if not isinstance(upload_ids, list):
                self._response(data={'parameter invalid': 'image_upload_id'})
            for uid in upload_ids:
                uid = uid.strip()
                if uid == '':
                    continue
                upload_id = uid
                break
            if upload_id is None:
                self._response(data={'parameter invalid': 'image_upload_id'})

            data = data.get(upload_id)
            if not isinstance(data, dict):
                self._error_not_found()
                return

            sorted_tags = [
                {
                    'confidence': data[tag],
                    'tag': {
                        'en': tag,
                    },
                }
                for tag in data
            ]

            if len(sorted_tags) == 0:
                self._response(
                    {
                        'result': {},
                        'status': {
                            'text': 'no tags',
                            'type': 'error',
                        },
                    }
                )
                return

            # order descending by confidence
            sorted_tags.sort(key=lambda t: t['confidence'], reverse=True)

            self._response(
                {
                    'result': {
                        'tags': sorted_tags,
                    },
                    'status': {
                        'text': '',
                        'type': 'success',
                    },
                }
            )
            return

        # -----------------------------------------------------------------

        self._error_not_found()

    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)

        if self.path in ['/', '/endpoints']:
            self._response_endpoints()
            return

        self._load_jobs()

        # for each image, simulate tagging with ai
        # select a random number of unique tags and store in the jobs map
        # for each tag generate a random confidence (not returned by all services)

        self._load_random_tags()

        tags = {}
        max_number_of_tags = random.randint(0, 10)
        while len(tags) < max_number_of_tags:
            random_tag = self.random_tags[random.randint(0, len(self.random_tags) - 1)]
            if random_tag in tags:
                continue
            # random confidence between 50.0% and 95.0%
            tags[random_tag] = float(random.randint(50, 95))

        # -----------------------------------------------------------------
        # endpoints for Cloudsight
        # - post image, return new job: '.../images'

        if self.path == '/cloudsight/images':
            job_id = self._insert_new_job('cloudsight', tags)
            self._response(
                data={
                    'token': job_id,
                    'status': 'not completed',
                }
            )
            return

        # -----------------------------------------------------------------
        # endpoints for DeepVA
        # post image, return new job: '.../jobs'
        # https://docs.deepva.com/core-resources/job/

        if self.path == '/deepva/jobs':
            job_id = self._insert_new_job('deepva', tags)
            self._response(
                data={
                    'id': job_id,
                    'state': 'processing',
                }
            )
            return

        # -----------------------------------------------------------------
        # endpoints for Imagga
        # post image, return new job: '.../uploads'
        # https://docs.imagga.com/#uploads

        if self.path == '/imagga/uploads':
            job_id = self._insert_new_job('imagga', tags)
            self._response(
                data={
                    'result': {
                        'upload_id': job_id,
                    },
                    'status': {
                        'type': 'success',
                    },
                }
            )
            return

        # -----------------------------------------------------------------

        self._error_not_found()


def run(
    server_class=HTTPServer,
    handler_class=AiDummyRequestHandler,
    port=8000,
):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print(f'Starting httpd server on port {port}...')

    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print(f'Stopping httpd server')


if __name__ == '__main__':
    run()
