import abc
import codecs
import json
import re

from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework import status
from rest_framework.exceptions import ParseError, APIException
from rest_framework.parsers import BaseParser

from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
from common.utils import get_logger

logger = get_logger(__file__)


class FileContentOverflowedError(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    default_code = 'file_content_overflowed'
    default_detail = _('The file content overflowed (The maximum length `{}` bytes)')


class BaseFileParser(BaseParser):
    FILE_CONTENT_MAX_LENGTH = 1024 * 1024 * 10

    serializer_cls = None
    serializer_fields = None
    obj_pattern = re.compile(r'^(.+)\(([a-z0-9-]+)\)$')

    def check_content_length(self, meta):
        content_length = int(meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0)))
        if content_length > self.FILE_CONTENT_MAX_LENGTH:
            msg = FileContentOverflowedError.default_detail.format(self.FILE_CONTENT_MAX_LENGTH)
            logger.error(msg)
            raise FileContentOverflowedError(msg)

    @staticmethod
    def get_stream_data(stream):
        stream_data = stream.read()
        stream_data = stream_data.strip(codecs.BOM_UTF8)
        return stream_data

    @abc.abstractmethod
    def generate_rows(self, stream_data):
        raise NotImplementedError

    def get_column_titles(self, rows):
        return next(rows)

    def convert_to_field_names(self, column_titles):
        fields_map = {}
        fields = self.serializer_fields
        for k, v in fields.items():
            # 资产平台的 id 是只读的, 导入更新资产平台会失败
            if v.read_only and k not in ['id', 'pk']:
                continue
            fields_map.update({
                v.label: k,
                k: k
            })
        lowercase_fields_map = {k.lower(): v for k, v in fields_map.items()}
        field_names = [
            lowercase_fields_map.get(column_title.strip('*').lower(), '')
            for column_title in column_titles
        ]
        return field_names

    @staticmethod
    def _replace_chinese_quote(s):
        if not isinstance(s, str):
            return s
        trans_table = str.maketrans({
            '“': '"',
            '”': '"',
            '‘': '"',
            '’': '"',
            '\'': '"'
        })
        return s.translate(trans_table)

    @classmethod
    def load_row(cls, row):
        """
        构建json数据前的行处理
        """
        new_row = []
        for col in row:
            # 转换中文引号
            col = cls._replace_chinese_quote(col)
            # 列表/字典转换
            if isinstance(col, str) and (
                    (col.startswith('[') and col.endswith(']')) or
                    (col.startswith("{") and col.endswith("}"))
            ):
                try:
                    col = json.loads(col)
                except json.JSONDecodeError as e:
                    logger.error('Json load error: ', e)
                    logger.error('col: ', col)
            new_row.append(col)
        return new_row

    def id_name_to_obj(self, v):
        if not v or not isinstance(v, str):
            return v
        matched = self.obj_pattern.match(v)
        if not matched:
            return v
        obj_name, obj_id = matched.groups()
        if obj_id.isdigit():
            obj_id = int(obj_id)
        return {'pk': obj_id, 'name': obj_name}

    def parse_value(self, field, value):
        if value == '-' and field and field.allow_null:
            return None
        elif hasattr(field, 'to_file_internal_value'):
            value = field.to_file_internal_value(value)
        elif isinstance(field, serializers.BooleanField):
            value = value.lower() in ['true', '1', 'yes']
        elif isinstance(field, ObjectRelatedField):
            if field.many:
                value = [self.id_name_to_obj(v) for v in value]
            else:
                value = self.id_name_to_obj(value)
        elif isinstance(field, LabeledChoiceField):
            value = self.id_name_to_obj(value)
            if isinstance(value, dict) and 'pk' in value:
                value = value.get('pk')
        elif isinstance(field, serializers.ListSerializer):
            value = [self.parse_value(field.child, v) for v in value]
        elif isinstance(field, serializers.Serializer):
            value = self.id_name_to_obj(value)
        elif isinstance(field, serializers.ManyRelatedField):
            value = [self.parse_value(field.child_relation, v) for v in value]
        elif isinstance(field, serializers.ListField):
            value = [self.parse_value(field.child, v) for v in value]

        return value

    def process_row_data(self, row_data):
        """
        构建json数据后的行数据处理
        """
        new_row = {}
        for k, v in row_data.items():
            field = self.serializer_fields.get(k)
            v = self.parse_value(field, v)
            new_row[k] = v
        return new_row

    def generate_data(self, fields_name, rows):
        data = []
        for row in rows:
            # 空行不处理
            if not any(row):
                continue
            row = self.load_row(row)
            row_data = dict(zip(fields_name, row))
            row_data = self.process_row_data(row_data)
            data.append(row_data)
        return data

    @staticmethod
    def pop_help_text_if_need(rows):
        rows = list(rows)
        if not rows:
            return rows
        if rows[0][0].startswith('#Help'):
            rows.pop(0)
        return rows

    def parse(self, stream, media_type=None, parser_context=None):
        assert parser_context is not None, '`parser_context` should not be `None`'

        view = parser_context['view']
        request = view.request

        try:
            meta = request.META
            self.serializer_cls = view.get_serializer_class()
            self.serializer_fields = self.serializer_cls().fields
        except Exception as e:
            logger.debug(e, exc_info=True)
            raise ParseError('The resource does not support imports!')

        self.check_content_length(meta)
        try:
            stream_data = self.get_stream_data(stream)
            rows = self.generate_rows(stream_data)
            column_titles = self.get_column_titles(rows)
            field_names = self.convert_to_field_names(column_titles)

            # 给 `common.mixins.api.RenderToJsonMixin` 提供，暂时只能耦合
            column_title_field_pairs = list(zip(column_titles, field_names))
            column_title_field_pairs = [(k, v) for k, v in column_title_field_pairs if k and v]
            if not hasattr(request, 'jms_context'):
                request.jms_context = {}
            request.jms_context['column_title_field_pairs'] = column_title_field_pairs

            rows = self.pop_help_text_if_need(rows)
            data = self.generate_data(field_names, rows)
            return data
        except Exception as e:
            logger.error(e, exc_info=True)
            raise ParseError(_('Parse file error: {}').format(e))
