import json
import requests

from auto_keyworder_modules.ai_services import ai_service, ai_service_configuration
from auto_keyworder_modules import eas, event


class DeepvaAiService(ai_service.AiServiceInterface):

    # default language (todo: get from response?)
    language: str = 'en-US'

    states_wait: list[str] = ['waiting', 'processing']
    states_success: list[str] = ['completed']
    states_fail: list[str] = ['stopped', 'failed']

    modules: dict = {}

    def __init__(
        self,
        config: ai_service_configuration.DeepvaConfiguration,
        easydb_server: str,
        easydb_token: str,
        logger=None,
    ) -> None:
        super().__init__(config, easydb_server, easydb_token, logger)

        # load modules from config
        self.modules = config.get_object(variable='modules', default={})

    def format_headers(self) -> dict[str]:
        return {
            'Content-Type': 'application/json',
            'Authorization': f'Key {self.config.get_string("api_key")}',
        }

    def check_state(self, response_json: dict) -> tuple[int, str, list[str]]:
        state = response_json.get('state')

        errors: list[str] = []
        _errors = response_json.get('errors')
        if isinstance(_errors, list):
            errors = [str(e) for e in _errors]

        if state in self.states_success:
            return self.STATE_SUCCESS, state, []

        if state in self.states_fail:
            return self.STATE_FAIL, state, errors

        return self.STATE_WAIT, state, errors

    def api_uses_base64(self) -> bool:
        return False

    def upload_image_to_service_api(
        self,
        asset_status: eas.AssetStatus,
    ) -> None:

        url = f'{self.config.get_api_url()}/jobs'

        response = requests.post(
            url=url,
            headers=self.format_headers(),
            json={
                'sources': [
                    asset_status.img_url,
                ],
                'modules': self.modules,
            },
        )

        event.log_event(
            easydb_server=self.easydb_server,
            easydb_token=self.easydb_token,
            event_data=event.format_request_event(
                event_type=event.AUTO_KEYWORDER_REQUEST_DEEPVA,
                asset_status=asset_status,
                request_method='POST',
                request_url=url,
                request_body={
                    'sources': [
                        asset_status.img_url,
                    ],
                    'modules': self.modules,
                },
                response_status_code=response.status_code,
                response_content=response.text,
            ),
        )

        if response.status_code not in [200, 202]:
            asset_status.failure_reason = f'/jobs: request failed with status={response.status_code}, response={response.text}'
            asset_status.failed = True
            return

        try:
            response_json = json.loads(response.text)

            # check state
            state, state_name, errors = self.check_state(response_json)
            if state == self.STATE_FAIL:
                asset_status.failure_reason = (
                    f'/jobs: response has state "{state_name}"'
                )
                if len(errors) > 0:
                    asset_status.failure_reason += f', errors: {"; ".join(errors)}'
                asset_status.failed = True
                return

            # get job id
            job_id = response_json.get('id')
            if not isinstance(job_id, str) or job_id == '':
                asset_status.failure_reason = (
                    f'/jobs: could not get a valid job id from response'
                )
                asset_status.failed = True
                return

            asset_status.ai_job_id = job_id

        except Exception as e:
            asset_status.failure_reason = (
                f'/jobs: could not parse response={response.text}: {e}'
            )
            asset_status.failed = True

    def get_ai_data_from_service_api(
        self,
        asset_status: eas.AssetStatus,
    ) -> int:

        url = f'{self.config.get_api_url()}/jobs/{asset_status.ai_job_id}'

        response = requests.get(
            url=url,
            headers=self.format_headers(),
        )

        event.log_event(
            easydb_server=self.easydb_server,
            easydb_token=self.easydb_token,
            event_data=event.format_request_event(
                event_type=event.AUTO_KEYWORDER_REQUEST_DEEPVA,
                asset_status=asset_status,
                request_method='GET',
                request_url=url,
                request_body=None,
                response_status_code=response.status_code,
                response_content=response.text,
            ),
        )

        if response.status_code not in [200, 202]:
            asset_status.failure_reason = f'/jobs: request failed with status={response.status_code}, response={response.text}'
            asset_status.failed = True
            return

        try:
            response_json = json.loads(response.text)

            # check state
            state, state_name, errors = self.check_state(response_json)
            if state != self.STATE_SUCCESS:
                if state == self.STATE_FAIL:
                    asset_status.failure_reason = (
                        f'/jobs: response has state "{state_name}"'
                    )
                    if len(errors) > 0:
                        asset_status.failure_reason += f', errors: {"; ".join(errors)}'
                    asset_status.failed = True
                return

            result = response_json.get('result')
            if not isinstance(result, dict):
                asset_status.failure_reason = (
                    f'/jobs: could not find valid "result" in response={response.text}'
                )
                asset_status.failed = True
                return

            summary = result.get('summary')
            if not isinstance(summary, list):
                asset_status.failure_reason = f'/jobs: could not find valid "result.summary" array in response={response.text}'
                asset_status.failed = True
                return

            parsed_labels = []
            seen_labels = set()
            for entry in summary:
                items = entry.get('items')
                if not isinstance(items, list):
                    continue

                for item in items:
                    label = item.get('label')
                    if not isinstance(label, str):
                        continue
                    label = label.strip()
                    if label == '':
                        continue

                    if label.lower() in seen_labels:
                        continue
                    seen_labels.add(label.lower())

                    parsed_labels.append(label)

            if len(parsed_labels) == 0:
                asset_status.failure_reason = f'/jobs: could not find valid label values in "result.summary[].items[].label" in response={response.text}'
                asset_status.failed = True
                return

            asset_status.ai_data = {
                'labels': parsed_labels,
            }
            asset_status.finished = True

        except Exception as e:
            asset_status.failure_reason = (
                f'/jobs: could not parse response={response.text}: {e}'
            )
            asset_status.failed = True

    def merge_ai_data_into_object(
        self,
        asset_status: eas.AssetStatus,
        linked_object_cache: dict[str, dict[str, dict[str, dict]]],
    ) -> bool:
        ot = asset_status.easydb_obj_objecttype()
        if ot != self.config.get_objecttype():
            return False

        target_field_info = self.config.get_field_info(
            field_variable='target_field',
            expected=False,
        )
        if not target_field_info:
            return False

        ai_labels = []
        # check if the ai data is valid
        if not isinstance(asset_status.ai_data, dict):
            return False
        labels = asset_status.ai_data.get('labels')
        if not isinstance(labels, list):
            return False
        for ai_label in labels:
            if ai_label == {}:
                continue
            ai_labels.append(ai_label)
        if len(ai_labels) == 0:
            return False

        sub_obj = asset_status.easydb_obj.get(ot)

        object_updated = False

        # iterate over the collected labels, append/insert into mapped field

        # check if in nested table or single field
        if target_field_info.is_in_nested():
            new_nested = []
            for ai_label in ai_labels:
                if target_field_info.is_linked():
                    # find/create a matching linked objects:
                    # look in cache, else search, else create a new linked object
                    link = self.handle_linked_object(
                        ai_data_to_use={
                            self.language: ai_label,
                        },
                        asset_status=asset_status,
                        language=self.language,
                        linked_object_cache=linked_object_cache,
                        field_info=target_field_info,
                    )
                    if link is None:
                        continue
                    new_nested.append({target_field_info.link_name: link})
                    asset_status.add_to_diff(
                        field_info=target_field_info,
                        field_value=ai_label,
                    )

                else:
                    # simple field in nested table
                    # check if l10n
                    if target_field_info.is_l10n():
                        new_nested.append(
                            {
                                target_field_info.name: {
                                    self.language: ai_label,
                                },
                            }
                        )
                        asset_status.add_to_diff(
                            field_info=target_field_info,
                            field_value=ai_label,
                        )
                    else:
                        new_nested.append(
                            {
                                target_field_info.name: ai_label,
                            }
                        )
                        asset_status.add_to_diff(
                            field_info=target_field_info,
                            field_value=ai_label,
                        )

            if len(new_nested) == 0:
                return False

            sub_obj[target_field_info.nested] = new_nested
            object_updated = True

        else:
            # not in nested -> concatenate alphabetically ordered values into single string
            ai_label_value = ', '.join(sorted(ai_labels))

            if target_field_info.is_linked():
                # find/create a matching linked object:
                # look in cache, else search, else create a new linked object
                link = self.handle_linked_object(
                    ai_data_to_use={
                        self.language: ai_label_value,
                    },
                    asset_status=asset_status,
                    language=self.language,
                    linked_object_cache=linked_object_cache,
                    field_info=target_field_info,
                )
                if link is None:
                    return False
                sub_obj[target_field_info.link_name] = link
                asset_status.add_to_diff(
                    field_info=target_field_info,
                    field_value=ai_label_value,
                )
                object_updated = True

            else:
                # simple field
                # check if l10n
                if target_field_info.is_l10n():
                    sub_obj[target_field_info.name] = {
                        self.language: ai_label_value,
                    }
                    asset_status.add_to_diff(
                        field_info=target_field_info,
                        field_value=ai_label_value,
                    )
                    object_updated = True
                else:
                    sub_obj[target_field_info.name] = ai_label_value
                    asset_status.add_to_diff(
                        field_info=target_field_info,
                        field_value=ai_label_value,
                    )
                    object_updated = True

        if not object_updated:
            # no new data was merged into the object
            return False

        self.update_main_object_info(asset_status.easydb_obj)
        return True
