from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _

from common.db.models import ChoicesMixin
from common.decorators import cached_method
from .base import FillType

__all__ = ['Protocol']


class Protocol(ChoicesMixin, models.TextChoices):
    ssh = 'ssh', 'SSH'
    sftp = 'sftp', 'SFTP'
    rdp = 'rdp', 'RDP'
    telnet = 'telnet', 'Telnet'
    vnc = 'vnc', 'VNC'
    winrm = 'winrm', 'WinRM'

    mysql = 'mysql', 'MySQL'
    mariadb = 'mariadb', 'MariaDB'
    oracle = 'oracle', 'Oracle'
    postgresql = 'postgresql', 'PostgreSQL'
    sqlserver = 'sqlserver', 'SQLServer'
    db2 = 'db2', 'DB2'
    dameng = 'dameng', 'Dameng'
    clickhouse = 'clickhouse', 'ClickHouse'
    redis = 'redis', 'Redis'
    mongodb = 'mongodb', 'MongoDB'

    k8s = 'k8s', 'K8s'
    http = 'http', 'HTTP(s)'

    chatgpt = 'chatgpt', 'ChatGPT'

    @classmethod
    def device_protocols(cls):
        return {
            cls.ssh: {
                'port': 22,
                'secret_types': ['password', 'ssh_key'],
                'setting': {
                    'old_ssh_version': {
                        'type': 'bool',
                        'default': False,
                        'label': _('Old SSH version'),
                        'help_text': _('Old SSH version like openssh 5.x or 6.x')
                    },
                    'nc': {
                        'type': 'bool',
                        'default': False,
                        'label': 'Netcat (nc)',
                        'help_text': _('Netcat help text')
                    }
                }
            },
            cls.sftp: {
                'port': 22,
                'secret_types': ['password', 'ssh_key'],
                'setting': {
                    'sftp_home': {
                        'type': 'str',
                        'default': '/tmp',
                        'label': _('SFTP root'),
                        'help_text': _(
                            'SFTP root directory, Support variable: <br>'
                            '- ${ACCOUNT} The connected account username <br>'
                            '- ${HOME} The home directory of the connected account <br>'
                            '- ${USER} The username of the user'
                        )
                    }
                }
            },
            cls.rdp: {
                'port': 3389,
                'secret_types': ['password'],
                'setting': {
                    'console': {
                        'type': 'bool',
                        'default': False,
                        'label': _('Console'),
                        'help_text': _("Connect to console session")
                    },
                    'security': {
                        'type': 'choice',
                        'choices': [('any', _('Any')), ('rdp', 'RDP'), ('tls', 'TLS'), ('nla', 'NLA')],
                        'default': 'any',
                        'label': _('Security'),
                        'help_text': _("Security layer to use for the connection:<br>"
                                       "Any<br>"
                                       "Automatically select the security mode based on the security protocols "
                                       "supported by both the client and the server<br>"
                                       "RDP<br>"
                                       "Legacy RDP encryption. This mode is generally only used for older Windows "
                                       "servers or in cases where a standard Windows login screen is desired<br>"
                                       "TLS<br>"
                                       "RDP authentication and encryption implemented via TLS.<br>"
                                       "NLA<br>"
                                       "This mode uses TLS encryption and requires the username and password "
                                       "to be given in advance")
                    },
                    'ad_domain': {
                        'type': 'str',
                        'required': False,
                        'default': '',
                        'label': _('AD domain')
                    }
                }
            },
            cls.vnc: {
                'port': 5900,
                'secret_types': ['password'],
            },
            cls.telnet: {
                'port': 23,
                'secret_types': ['password'],
                'setting': {
                    'username_prompt': {
                        'type': 'str',
                        'default': 'username:|login:',
                        'label': _('Username prompt'),
                        'help_text': _('We will send username when we see this prompt')
                    },
                    'password_prompt': {
                        'type': 'str',
                        'default': 'password:',
                        'label': _('Password prompt'),
                        'help_text': _('We will send password when we see this prompt')
                    },
                    'success_prompt': {
                        'type': 'str',
                        'default': 'success|成功|#|>|\$',
                        'label': _('Success prompt'),
                        'help_text': _('We will consider login success when we see this prompt')
                    }
                }
            },
            cls.winrm: {
                'port': 5985,
                'secret_types': ['password'],
                'setting': {
                    'use_ssl': {
                        'type': 'bool',
                        'default': False,
                        'label': _('Use SSL')
                    },
                }
            },
        }

    @classmethod
    def database_protocols(cls):
        return {
            cls.mysql: {
                'port': 3306,
                'setting': {},
                'required': True,
                'secret_types': ['password'],
            },
            cls.mariadb: {
                'port': 3306,
                'required': True,
                'secret_types': ['password'],
            },
            cls.postgresql: {
                'port': 5432,
                'required': True,
                'secret_types': ['password'],
                'xpack': True
            },
            cls.oracle: {
                'port': 1521,
                'required': True,
                'secret_types': ['password'],
                'xpack': True,
                'setting': {
                    'sysdba': {
                        'type': 'bool',
                        'default': False,
                        'label': _('SYSDBA'),
                        'help_text': _('Connect as SYSDBA')
                    },
                }
            },
            cls.sqlserver: {
                'port': 1433,
                'required': True,
                'secret_types': ['password'],
                'xpack': True,
                'setting': {
                    'version': {
                        'type': 'choice',
                        'choices': [('>=2014', '>= 2014'), ('<2014', '< 2014')],
                        'default': '>=2014',
                        'label': _('Version'),
                        'help_text': _('SQL Server version, Different versions have different connection drivers')
                    },
                    'encrypt': {
                        'type': 'bool',
                        'default': True,
                        'label': _('Encrypt'),
                        'help_text': _('Whether to use TLS encryption.')
                    }
                }
            },
            cls.db2: {
                'port': 5000,
                'required': True,
                'secret_types': ['password'],
                'xpack': True,
            },
            cls.dameng: {
                'port': 5236,
                'required': True,
                'secret_types': ['password'],
                'xpack': True,
            },
            cls.clickhouse: {
                'port': 9000,
                'required': True,
                'secret_types': ['password'],
                'xpack': True,
            },
            cls.mongodb: {
                'port': 27017,
                'required': True,
                'secret_types': ['password'],
                'setting': {
                    'auth_source': {
                        'type': 'str',
                        'default': 'admin',
                        'label': _('Auth source'),
                        'help_text': _('The database to authenticate against')
                    },
                    'connection_options': {
                        'type': 'str',
                        'default': '',
                        'label': _('Connect options'),
                        'help_text': _('The connection specific options eg. retryWrites=false&retryReads=false')
                    }
                }
            },
            cls.redis: {
                'port': 6379,
                'required': True,
                'secret_types': ['password'],
                'setting': {
                    'auth_username': {
                        'type': 'bool',
                        'default': False,
                        'label': _('Auth username')
                    },
                    'enable_cluster_mode': {
                        'type': 'bool',
                        'default': False,
                        'label': _('Enable cluster mode'),
                        'help_text': _('Enable if this Redis instance is part of a cluster')
                    },
                }
            },
        }

    @classmethod
    def cloud_protocols(cls):
        return {
            cls.k8s: {
                'port': 443,
                'port_from_addr': True,
                'required': True,
                'secret_types': ['token'],
                'setting': {
                    'namespace': {
                        'type': 'str',
                        'required': False,
                        'default': '',
                        'label': _('Namespace')
                    }
                }
            },
            cls.http: {
                'port': 80,
                'port_from_addr': True,
                'secret_types': ['password'],
                'setting': {
                    'safe_mode': {
                        'type': 'bool',
                        'default': False,
                        'label': _('Safe mode'),
                        'help_text': _(
                            'When safe mode is enabled, some operations will be disabled, such as: '
                            'New tab, right click, visit other website, etc.'
                        )
                    },
                    'autofill': {
                        'label': _('Autofill'),
                        'type': 'choice',
                        'choices': FillType.choices,
                        'default': 'basic',
                    },
                    'username_selector': {
                        'type': 'str',
                        'default': 'name=username',
                        'label': _('Username selector')
                    },
                    'password_selector': {
                        'type': 'str',
                        'default': 'name=password',
                        'label': _('Password selector')
                    },
                    'submit_selector': {
                        'type': 'str',
                        'default': 'type=submit',
                        'label': _('Submit selector')
                    },
                    'script': {
                        'type': 'text',
                        'default': [],
                        'label': _('Script'),
                    }
                }
            },
        }

    @classmethod
    def gpt_protocols(cls):
        protocols = {
            cls.chatgpt: {
                'port': 443,
                'required': True,
                'port_from_addr': True,
                'secret_types': ['api_key'],
                'setting': {
                    'api_mode': {
                        'type': 'choice',
                        'default': 'gpt-4o-mini',
                        'label': _('API mode'),
                        'choices': [
                            ('gpt-4o-mini', 'GPT-4o-mini'),
                            ('gpt-4o', 'GPT-4o'),
                            ('gpt-4-turbo', 'GPT-4 Turbo'),
                        ]
                    }
                }
            }
        }
        return protocols

    @classmethod
    @cached_method(ttl=600)
    def settings(cls):
        return {
            **cls.device_protocols(),
            **cls.database_protocols(),
            **cls.cloud_protocols(),
            **cls.gpt_protocols(),
        }

    @classmethod
    @cached_method(ttl=600)
    def protocols(cls):
        protocols = []
        xpack_enabled = settings.XPACK_ENABLED
        for protocol, config in cls.settings().items():
            if not xpack_enabled and config.get('xpack', False):
                continue
            protocols.append({'label': protocol.label, 'value': protocol.value})

        from assets.models.platform import PlatformProtocol
        custom_protocols = (
            PlatformProtocol.objects
            .filter(platform__category='custom')
            .values_list('name', flat=True)
            .distinct()
        )
        for protocol in custom_protocols:
            if protocol not in protocols:
                if not protocol:
                    continue
                label = protocol[0].upper() + protocol[1:]
                protocols.append({'label': label, 'value': protocol})
        return protocols

    @classmethod
    @cached_method(ttl=600)
    def xpack_protocols(cls):
        return [
            protocol for protocol, config in cls.settings().items()
            if config.get('xpack', False)
        ]

    @classmethod
    def protocol_secret_types(cls):
        configs = cls.settings()
        return {
            protocol: configs[protocol]['secret_types'] or ['password']
            for protocol in configs
        }
