from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.validators import UniqueValidator

from assets.models import Asset
from common.serializers import (
    WritableNestedModelSerializer, type_field_map, MethodSerializer,
    DictSerializer, create_serializer_class, ResourceLabelsMixin
)
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import lazyproperty
from ..const import Category, AllTypes, Protocol, SuMethodChoices
from ..models import Platform, PlatformProtocol, PlatformAutomation

__all__ = ["PlatformSerializer", "PlatformOpsMethodSerializer", "PlatformProtocolSerializer", "PlatformListSerializer"]


class PlatformAutomationSerializer(serializers.ModelSerializer):
    class Meta:
        model = PlatformAutomation
        fields = [
            "id",
            "ansible_enabled", "ansible_config",
            "ping_enabled", "ping_method", "ping_params",
            "push_account_enabled", "push_account_method", "push_account_params",
            "gather_facts_enabled", "gather_facts_method", "gather_facts_params",
            "change_secret_enabled", "change_secret_method", "change_secret_params",
            "verify_account_enabled", "verify_account_method", "verify_account_params",
            "gather_accounts_enabled", "gather_accounts_method", "gather_accounts_params",
            "remove_account_enabled", "remove_account_method", "remove_account_params",
        ]
        extra_kwargs = {
            # 启用资产探测
            "ping_enabled": {"label": _("Ping enabled"), "help_text": _("Enable asset detection")},
            "ping_method": {"label": _("Ping method")},
            "gather_facts_enabled": {
                "label": _("Gather facts enabled"),
                "help_text": _("Enable asset information collection")
            },
            "gather_facts_method": {
                "label": _("Gather facts method"),
            },
            "verify_account_enabled": {
                "label": _("Verify account enabled"),
                "help_text": _("Enable account verification")
            },
            "verify_account_method": {
                "label": _("Verify account method"),
            },
            "change_secret_enabled": {
                "label": _("Change secret enabled"),
                "help_text": _("Enable account secret auto change")
            },
            "change_secret_method": {
                "label": _("Change secret method"),
            },
            "push_account_enabled": {
                "label": _("Push account enabled"),
                "help_text": _("Enable account auto push")
            },
            "push_account_method": {
                "label": _("Push account method"),
            },
            "gather_accounts_enabled": {
                "label": _("Gather accounts enabled"),
                "help_text": _("Enable account collection")
            },
            "gather_accounts_method": {
                "label": _("Gather accounts method"),
            },
            "remove_account_method": {
                "label": _("Remove account method"),
            },
            "remove_account_enabled": {
                "label": _("Remove accounts enabled"),
                "help_text": _("Enable account remove"),
            },

        }


class PlatformProtocolSerializer(serializers.ModelSerializer):
    setting = MethodSerializer(required=False, label=_("Setting"))
    port_from_addr = serializers.BooleanField(label=_("Port from addr"), read_only=True)

    class Meta:
        model = PlatformProtocol
        fields = [
            "id", "name", "port", "port_from_addr",
            "primary", "required", "default", "public",
            "secret_types", "setting",
        ]
        extra_kwargs = {
            "primary": {
                "help_text": _(
                    "This protocol is primary, and it must be set when adding assets. "
                    "Additionally, there can only be one primary protocol."
                )
            },
            "required": {
                "help_text": _("This protocol is required, and it must be set when adding assets.")
            },
            "default": {
                "help_text": _("This protocol is default, when adding assets, it will be displayed by default.")
            },
            "public": {
                "help_text": _("This protocol is public, asset will show this protocol to user")
            },
        }

    def get_setting_serializer(self):
        request = self.context.get('request')
        default_field = DictSerializer(required=False)

        if not request:
            return default_field

        if self.instance and isinstance(self.instance, (QuerySet, list)):
            instance = self.instance[0]
        else:
            instance = self.instance

        protocol = request.query_params.get('name', '')
        if instance and not protocol:
            protocol = instance.name

        protocol_settings = Protocol.settings()
        setting_fields = protocol_settings.get(protocol, {}).get('setting')
        if not setting_fields:
            return default_field

        setting_fields = [{'name': k, **v} for k, v in setting_fields.items()]
        name = '{}ProtocolSettingSerializer'.format(protocol.capitalize())
        return create_serializer_class(name, setting_fields)()

    def validate(self, cleaned_data):
        name = cleaned_data.get('name')
        if name in ['winrm']:
            cleaned_data['public'] = False
        return cleaned_data

    def to_file_representation(self, data):
        return '{name}/{port}'.format(**data)

    def to_file_internal_value(self, data):
        name, port = data.split('/')
        return {'name': name, 'port': port}

    @staticmethod
    def get_render_help_text():
        return _('Protocols, format is ["protocol/port"]')


class PlatformCustomField(serializers.Serializer):
    TYPE_CHOICES = [(t, t) for t, c in type_field_map.items()]
    name = serializers.CharField(label=_("Name"), max_length=128)
    label = serializers.CharField(label=_("Label"), max_length=128)
    type = serializers.ChoiceField(choices=TYPE_CHOICES, label=_("Type"), default='str')
    default = serializers.CharField(default="", allow_blank=True, label=_("Default"), max_length=1024)
    help_text = serializers.CharField(default="", allow_blank=True, label=_("Help text"), max_length=1024)
    choices = serializers.ListField(default=list, label=_("Choices"), required=False)


class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
    id = serializers.IntegerField(
        label='ID', required=False,
        validators=[UniqueValidator(queryset=Platform.objects.all())]
    )
    charset = LabeledChoiceField(choices=Platform.CharsetChoices.choices, label=_("Charset"), default='utf-8')
    type = LabeledChoiceField(choices=AllTypes.choices(), label=_("Type"))
    category = LabeledChoiceField(choices=Category.choices, label=_("Category"))
    protocols = PlatformProtocolSerializer(label=_("Protocols"), many=True, required=False)
    automation = PlatformAutomationSerializer(label=_("Automation"), required=False, default=dict)
    su_method = LabeledChoiceField(
        choices=SuMethodChoices.choices, label=_("Su method"),
        required=False, default=SuMethodChoices.sudo, allow_null=True
    )
    custom_fields = PlatformCustomField(label=_("Custom fields"), many=True, required=False)
    assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Assets'))
    assets_amount = serializers.IntegerField(label=_('Assets amount'), read_only=True)

    class Meta:
        model = Platform
        fields_mini = ["id", "name", "internal"]
        fields_small = fields_mini + [
            "category", "type", "charset",
        ]
        fields_unexport = ['automation']
        read_only_fields = [
            'internal', 'date_created', 'date_updated',
            'created_by', 'updated_by'
        ]
        fields_m2m = ['assets', 'assets_amount']
        fields = fields_small + fields_m2m + [
            "protocols", "domain_enabled", "su_enabled", "su_method",
            "automation", "comment", "custom_fields", "labels"
        ] + read_only_fields
        extra_kwargs = {
            "su_enabled": {
                "label": _('Su enabled'),
                "help_text": _(
                    "Login with account when accessing assets, then automatically switch to another, "
                    "similar to logging in with a regular account and then switching to root"
                )
            },
            "domain_enabled": {
                "label": _('Gateway enabled'),
                "help_text": _("Assets can be connected using a zone gateway")
            },
            "domain_default": {"label": _('Default Domain')},
            'assets': {'required': False, 'label': _('Assets')},
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_initial_value()

    def set_initial_value(self):
        if not hasattr(self, 'initial_data'):
            return
        if self.instance:
            return
        if not self.initial_data.get('automation'):
            self.initial_data['automation'] = {}

    @property
    def platform_category_type(self):
        if self.instance:
            return self.instance.category, self.instance.type
        if self.initial_data:
            return self.initial_data.get('category'), self.initial_data.get('type')
        raise serializers.ValidationError({'type': _("type is required")})

    def add_type_choices(self, name, label):
        tp = self.fields['type']
        tp.choices[name] = label
        tp.choice_strings_to_values[name] = label

    @lazyproperty
    def constraints(self):
        category, tp = self.platform_category_type
        constraints = AllTypes.get_constraints(category, tp)
        return constraints

    def validate_protocols(self, protocols):
        if not protocols:
            raise serializers.ValidationError(_("Protocols is required"))
        primary = [p for p in protocols if p.get('primary')]
        if not primary:
            protocols[0]['primary'] = True
        # 这里不设置不行,write_nested 不使用 validated 中的
        self.initial_data['protocols'] = protocols
        return protocols

    def validate_su_enabled(self, su_enabled):
        return su_enabled and self.constraints.get('su_enabled', False)

    def validate_domain_enabled(self, domain_enabled):
        return domain_enabled and self.constraints.get('domain_enabled', False)

    def validate_automation(self, automation):
        automation = automation or {}
        ansible_enabled = automation.get('ansible_enabled', False) \
                          and self.constraints['automation'].get('ansible_enabled', False)
        automation['ansible_enable'] = ansible_enabled
        return automation


class PlatformListSerializer(PlatformSerializer):
    class Meta(PlatformSerializer.Meta):
        fields = list(set(PlatformSerializer.Meta.fields + ['assets_amount']) - {'assets'})


class PlatformOpsMethodSerializer(serializers.Serializer):
    id = serializers.CharField(read_only=True)
    name = serializers.CharField(max_length=50, label=_("Name"))
    category = serializers.CharField(max_length=50, label=_("Category"))
    type = serializers.ListSerializer(child=serializers.CharField())
    method = serializers.CharField()