# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# 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 typing import List, Dict, Optional, TypedDict
from pydantic import BaseModel, Field

from common.proto.acl_pb2 import AclForwardActionEnum, AclRuleTypeEnum, AclEntry
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule

class Ipv4(BaseModel):
    dscp: int = 0
    source_ipv4_network: str = Field(serialization_alias="source-ipv4-network", default="") 
    destination_ipv4_network: str = Field(serialization_alias="destination-ipv4-network", default="") 

class Port(BaseModel):
    port: int = 0
    operator: str = "eq"

class Tcp(BaseModel):
    flags: str = ""
    source_port: Port = Field(serialization_alias="source-port", default_factory=lambda: Port())
    destination_port: Port = Field(serialization_alias="destination-port", default_factory=lambda: Port())

class Matches(BaseModel):
    ipv4: Ipv4 = Ipv4()
    tcp: Tcp = Tcp()

class Action(BaseModel):
    forwarding: str = ""

class Ace(BaseModel):
    name: str = "custom_rule"
    matches: Matches = Matches()
    actions: Action = Action()

class Aces(BaseModel):
    ace: List[Ace] = [Ace()]

class Acl(BaseModel):
    name: str = ""
    type: str = ""
    aces: Aces = Aces()

class Name(BaseModel):
    name: str = ""

class AclSet(BaseModel):
    acl_set: List[Name] = Field(serialization_alias="acl-set", default=[Name()])

class AclSets(BaseModel):
    acl_sets: AclSet = Field(serialization_alias="acl-sets", default=AclSet())

class Ingress(BaseModel):
    ingress: AclSets = AclSets()

class Interface(BaseModel):
    interface_id: str = Field(serialization_alias="interface-id", default="")
    ingress: Ingress = Ingress()

class Interfaces(BaseModel):
    interface: List[Interface] = [Interface()]

class AttachmentPoints(BaseModel):
    attachment_points: Interfaces = Field(serialization_alias="attachment-points", default=Interfaces())

class Acls(BaseModel):
    acl: List[Acl] = [Acl()]
    attachment_points: AttachmentPoints = Field(serialization_alias="attachment-points", default=AttachmentPoints())

class IETF_ACL(BaseModel):
    acls: Acls = Acls()
    

IETF_TFS_RULE_TYPE_MAPPING = {
    "ipv4-acl-type": "ACLRULETYPE_IPV4",
    "ipv6-acl-type": "ACLRULETYPE_IPV6",
}

IETF_TFS_FORWARDING_ACTION_MAPPING = {
    "drop": "ACLFORWARDINGACTION_DROP",
    "accept": "ACLFORWARDINGACTION_ACCEPT",
}

TFS_IETF_RULE_TYPE_MAPPING = {
    "ACLRULETYPE_IPV4": "ipv4-acl-type",
    "ACLRULETYPE_IPV6":     "ipv6-acl-type",
}

TFS_IETF_FORWARDING_ACTION_MAPPING = {
    "ACLFORWARDINGACTION_DROP":     "drop",
    "ACLFORWARDINGACTION_ACCEPT": "accept",
}

def config_rule_from_ietf_acl(
    request: Dict,
    device_uuid: str,
    endpoint_uuid: str,
    sequence_id: int,
    subinterface: int,
) -> ConfigRule:
    the_acl = request["ietf-access-control-list"]["acls"]["acl"][0]
    acl_ip_data = the_acl["aces"]["ace"][0]["matches"]["ipv4"]
    acl_tcp_data = the_acl["aces"]["ace"][0]["matches"]["tcp"]
    attachemnt_interface = request["ietf-access-control-list"]["acls"]['attachment-points']['interface'][0]
    source_address = acl_ip_data["source-ipv4-network"]
    destination_address = acl_ip_data["destination-ipv4-network"]
    source_port = acl_tcp_data['source-port']['port']
    destination_port = acl_tcp_data['destination-port']['port']
    ietf_action = the_acl["aces"]["ace"][0]["actions"]["forwarding"]
    interface_id = attachemnt_interface['interface-id']

    acl_config_rule = ConfigRule()
    acl_config_rule.action = ConfigActionEnum.CONFIGACTION_SET
    acl_config_rule.acl.interface = interface_id
    acl_endpoint_id = acl_config_rule.acl.endpoint_id
    acl_endpoint_id.device_id.device_uuid.uuid = device_uuid
    acl_endpoint_id.endpoint_uuid.uuid = endpoint_uuid
    acl_rule_set = acl_config_rule.acl.rule_set
    acl_rule_set.name = the_acl["name"]
    acl_rule_set.type = getattr(AclRuleTypeEnum, IETF_TFS_RULE_TYPE_MAPPING[the_acl['type']])
    acl_rule_set.description = (
        f'{ietf_action} {the_acl["type"]}: {source_address}:{source_port}->{destination_address}:{destination_port}'
    )
    acl_entry = AclEntry()
    acl_entry.sequence_id = sequence_id
    acl_entry.match.src_address = source_address
    acl_entry.match.dst_address = destination_address
    acl_entry.match.src_port = source_port
    acl_entry.match.dst_port = destination_port
    acl_entry.match.dscp = acl_ip_data["dscp"]
    acl_entry.match.flags = acl_tcp_data["flags"]
    acl_entry.action.forward_action = getattr(AclForwardActionEnum, IETF_TFS_FORWARDING_ACTION_MAPPING[ietf_action])
    acl_rule_set.entries.append(acl_entry)

    return acl_config_rule

def ietf_acl_from_config_rule_resource_value(config_rule_rv: Dict) -> Dict:
    rule_set = config_rule_rv['rule_set']
    acl_entry = rule_set['entries'][0]
    match_ = acl_entry['match']

    ipv4 = Ipv4(dscp=match_["dscp"], source_ipv4_network=match_["src_address"], destination_ipv4_network=match_["dst_address"])
    tcp = Tcp(flags=match_["flags"], source_port=Port(port=match_["src_port"]), destination_port=Port(port=match_["dst_port"]))
    matches = Matches(ipvr=ipv4, tcp=tcp)
    aces = Aces(ace=[Ace(matches=matches, actions=Action(forwarding=TFS_IETF_FORWARDING_ACTION_MAPPING[acl_entry["action"]["forward_action"]]))])
    acl = Acl(name=rule_set["name"], type=TFS_IETF_RULE_TYPE_MAPPING[rule_set["type"]], aces=aces)
    acl_sets = AclSets(acl_sets=AclSet(acl_set=[Name(name=rule_set["name"])]))
    ingress = Ingress(ingress=acl_sets)
    interfaces = Interfaces(interface=[Interface(interface_id=config_rule_rv["interface"], ingress=ingress)])
    acls = Acls(acl=[acl], attachment_points=AttachmentPoints(attachment_points=interfaces))
    ietf_acl = IETF_ACL(acls=acls)

    return ietf_acl.model_dump(by_alias=True)
