#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
#    All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from neutron_lib.db import constants as db_const
from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy import orm

from neutron.db.models import l3
from neutron.db import models_v2

from neutron_vpnaas.services.vpn.common import constants


AUTH_ALGORITHM_ENUM_VALUES = [
    'sha1', 'sha256', 'sha384', 'sha512',
    'aes-xcbc', 'aes-cmac',
]

ENCRYPTION_ALGORITHM_ENUM_VALUES = [
    '3des',
    'aes-128', 'aes-192', 'aes-256',
    'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr',
    'aes-128-ccm-8', 'aes-192-ccm-8', 'aes-256-ccm-8',
    'aes-128-ccm-12', 'aes-192-ccm-12', 'aes-256-ccm-12',
    'aes-128-ccm-16', 'aes-192-ccm-16', 'aes-256-ccm-16',
    'aes-128-gcm-8', 'aes-192-gcm-8', 'aes-256-gcm-8',
    'aes-128-gcm-12', 'aes-192-gcm-12', 'aes-256-gcm-12',
    'aes-128-gcm-16', 'aes-192-gcm-16', 'aes-256-gcm-16',
]

PFS_ENUM_VALUES = [
    'group2', 'group5', 'group14', 'group15',
    'group16', 'group17', 'group18', 'group19', 'group20', 'group21',
    'group22', 'group23', 'group24', 'group25', 'group26', 'group27',
    'group28', 'group29', 'group30', 'group31',
]


class IPsecPeerCidr(model_base.BASEV2):
    """Internal representation of a IPsec Peer Cidrs."""

    cidr = sa.Column(sa.String(32), nullable=False, primary_key=True)
    ipsec_site_connection_id = sa.Column(
        sa.String(36),
        sa.ForeignKey('ipsec_site_connections.id',
                      ondelete="CASCADE"),
        primary_key=True)


class IPsecPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject):
    """Represents a v2 IPsecPolicy Object."""
    __tablename__ = 'ipsecpolicies'
    name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
    description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
    transform_protocol = sa.Column(sa.Enum("esp", "ah", "ah-esp",
                                           name="ipsec_transform_protocols"),
                                   nullable=False)
    auth_algorithm = sa.Column(sa.Enum(*AUTH_ALGORITHM_ENUM_VALUES,
                                       name="vpn_auth_algorithms"),
                               nullable=False)
    encryption_algorithm = sa.Column(sa.Enum(*ENCRYPTION_ALGORITHM_ENUM_VALUES,
                                             name="vpn_encrypt_algorithms"),
                                     nullable=False)
    encapsulation_mode = sa.Column(sa.Enum("tunnel", "transport",
                                           name="ipsec_encapsulations"),
                                   nullable=False)
    lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes",
                                       name="vpn_lifetime_units"),
                               nullable=False)
    lifetime_value = sa.Column(sa.Integer, nullable=False)
    pfs = sa.Column(sa.Enum(*PFS_ENUM_VALUES, name="vpn_pfs"), nullable=False)


class IKEPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject):
    """Represents a v2 IKEPolicy Object."""
    __tablename__ = 'ikepolicies'
    name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
    description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
    auth_algorithm = sa.Column(sa.Enum(*AUTH_ALGORITHM_ENUM_VALUES,
                                       name="vpn_auth_algorithms"),
                               nullable=False)
    encryption_algorithm = sa.Column(sa.Enum(*ENCRYPTION_ALGORITHM_ENUM_VALUES,
                                             name="vpn_encrypt_algorithms"),
                                     nullable=False)
    phase1_negotiation_mode = sa.Column(sa.Enum("main", 'aggressive',
                                                name="ike_phase1_mode"),
                                        nullable=False)
    lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes",
                                       name="vpn_lifetime_units"),
                               nullable=False)
    lifetime_value = sa.Column(sa.Integer, nullable=False)
    ike_version = sa.Column(sa.Enum("v1", "v2", name="ike_versions"),
                            nullable=False)
    pfs = sa.Column(sa.Enum(*PFS_ENUM_VALUES, name="vpn_pfs"), nullable=False)


class IPsecSiteConnection(model_base.BASEV2, model_base.HasId,
                          model_base.HasProject):
    """Represents a IPsecSiteConnection Object."""
    __tablename__ = 'ipsec_site_connections'
    name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
    description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
    peer_address = sa.Column(sa.String(255), nullable=False)
    peer_id = sa.Column(sa.String(255), nullable=False)
    local_id = sa.Column(sa.String(255), nullable=True)
    route_mode = sa.Column(sa.String(8), nullable=False)
    mtu = sa.Column(sa.Integer, nullable=False)
    initiator = sa.Column(sa.Enum("bi-directional", "response-only",
                                  name="vpn_initiators"), nullable=False)
    auth_mode = sa.Column(sa.String(16), nullable=False)
    psk = sa.Column(sa.String(255), nullable=False)
    dpd_action = sa.Column(sa.Enum("hold", "clear",
                                   "restart", "disabled",
                                   "restart-by-peer", name="vpn_dpd_actions"),
                           nullable=False)
    dpd_interval = sa.Column(sa.Integer, nullable=False)
    dpd_timeout = sa.Column(sa.Integer, nullable=False)
    status = sa.Column(sa.String(16), nullable=False)
    admin_state_up = sa.Column(sa.Boolean(), nullable=False)
    vpnservice_id = sa.Column(sa.String(36),
                              sa.ForeignKey('vpnservices.id'),
                              nullable=False)
    ipsecpolicy_id = sa.Column(sa.String(36),
                               sa.ForeignKey('ipsecpolicies.id'),
                               nullable=False)
    ikepolicy_id = sa.Column(sa.String(36),
                             sa.ForeignKey('ikepolicies.id'),
                             nullable=False)
    ipsecpolicy = orm.relationship(
        IPsecPolicy, backref='ipsec_site_connection')
    ikepolicy = orm.relationship(IKEPolicy, backref='ipsec_site_connection')
    peer_cidrs = orm.relationship(IPsecPeerCidr,
                                  backref='ipsec_site_connection',
                                  lazy='joined',
                                  cascade='all, delete, delete-orphan')
    local_ep_group_id = sa.Column(sa.String(36),
                                  sa.ForeignKey('vpn_endpoint_groups.id'))
    peer_ep_group_id = sa.Column(sa.String(36),
                                 sa.ForeignKey('vpn_endpoint_groups.id'))
    local_ep_group = orm.relationship("VPNEndpointGroup",
                                      foreign_keys=local_ep_group_id)
    peer_ep_group = orm.relationship("VPNEndpointGroup",
                                     foreign_keys=peer_ep_group_id)


class VPNService(model_base.BASEV2, model_base.HasId, model_base.HasProject):
    """Represents a v2 VPNService Object."""
    name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
    description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
    status = sa.Column(sa.String(16), nullable=False)
    admin_state_up = sa.Column(sa.Boolean(), nullable=False)
    external_v4_ip = sa.Column(sa.String(16))
    external_v6_ip = sa.Column(sa.String(64))
    subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'))
    router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'),
                          nullable=False)
    subnet = orm.relationship(models_v2.Subnet)
    router = orm.relationship(l3.Router)
    ipsec_site_connections = orm.relationship(
        IPsecSiteConnection,
        backref='vpnservice',
        cascade="all, delete-orphan")
    flavor_id = sa.Column(sa.String(36), sa.ForeignKey(
        'flavors.id', name='fk_vpnservices_flavors_id'))


class VPNEndpoint(model_base.BASEV2):
    """Endpoints used in VPN connections.

    All endpoints in a group must be of the same type. Note: the endpoint
    is an 'opaque' field used to hold different endpoint types, and be
    flexible enough to use for future types.
    """
    __tablename__ = 'vpn_endpoints'
    endpoint = sa.Column(sa.String(255), nullable=False, primary_key=True)
    endpoint_group_id = sa.Column(sa.String(36),
                                  sa.ForeignKey('vpn_endpoint_groups.id',
                                                ondelete="CASCADE"),
                                  primary_key=True)


class VPNEndpointGroup(model_base.BASEV2, model_base.HasId,
                       model_base.HasProject):
    """Collection of endpoints of a specific type, for VPN connections."""
    __tablename__ = 'vpn_endpoint_groups'
    name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
    description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE))
    endpoint_type = sa.Column(sa.Enum(*constants.VPN_SUPPORTED_ENDPOINT_TYPES,
                                      name="endpoint_type"),
                              nullable=False)
    endpoints = orm.relationship(VPNEndpoint,
                                 backref='endpoint_group',
                                 lazy='joined',
                                 cascade='all, delete, delete-orphan')
