from django.contrib.auth.models import AnonymousUser
from django.db.models import Avg
from drf_extra_fields.geo_fields import PointField
from rest_framework import serializers

from apps.master.models import AmenitiesMaster, LocationMaster
from apps.master.serializers.master_serializers import (
    AmenitiesMasterSerializers,
    ConnectorMasterSerializers,
    LocationMasterSerializers,
)
from apps.station import models as station_model
from apps.station.services import station_services
from apps.station.services.station_services import (
    get_or_create_connector,
    get_or_create_network_operator,
)
from base.serializers import DynamicFieldsModelSerializer, DynamicFieldsSerializer
from station_ev import settings


class StationMediaSerializer(DynamicFieldsModelSerializer):
    """
    This is the StationMediaSerializer.

    It serializes station medias data from a CSV file.

    Meta:
        model (StationMedia): The model used for serialization.
        fields (str): The fields to include in the serialization (all fields are included).
    """

    class Meta:
        model = station_model.StationMedia
        fields = ("id", "asset", "asset_type", "is_verified")
        read_only_fields = ("station",)


class StationConnectorSerializer(DynamicFieldsModelSerializer):
    """
    This is the StationConnectorSerializer.

    It serializes station connectors data from a CSV file.

    Meta:
        model (StationConnectors): The model used for serialization.
        fields (str): The fields to include in the serialization (all fields are included).
    """

    connector_data = ConnectorMasterSerializers(
        source="connector", read_only=True, fields=["id", "name", "short_name", "image"]
    )

    class Meta:
        model = station_model.StationConnectors
        fields = (
            "id",
            "station",
            "connector",
            "kilowatts",
            "cost_description",
            "network_operator",
            "connector_data",
        )
        read_only_fields = ("id",)
        extra_kwargs = {"station": {"required": False}}


class StationAmenitiesSerializer(DynamicFieldsModelSerializer):
    """
    This is the StationAmenitiesSerializer.

    It serializes station amenities data from model.

    Meta:
        model (StationAmenities): The model used for serialization.
        fields (str): The fields to include in the serialization (all fields are included).
    """

    amenities = AmenitiesMasterSerializers(fields=["id", "name"])

    class Meta:
        model = station_model.StationAmenities
        fields = "__all__"
        read_only_fields = ("station",)


class StationLocationsSerializer(DynamicFieldsModelSerializer):
    """
    This is the StationLocationsSerializer.

    It serializes station locations data from model.

    Meta:
        model (StationLocation): The model used for serialization.
        fields (str): The fields to include in the serialization (all fields are included).
    """

    locations = LocationMasterSerializers(source="location", fields=["id", "name"])

    class Meta:
        model = station_model.StationLocation
        fields = "__all__"
        read_only_fields = ("station",)


class StationCSVUploadSerializers(DynamicFieldsModelSerializer):
    """
    This is the StationCSVUploadSerializers.

    It serializes station data from a CSV file.

    Attributes:
        coordinates (PointField): A GeoDjango PointField allowing the inclusion of location data.
        stations (SerializerMethodField): A dynamically generated field for retrieving and
        serializing station connectors.
        amenities (SerializerMethodField): A dynamically generated field for retrieving and
        serializing station amenities.


    Meta:
        model (StationMaster): The model used for serialization.
        fields (str): The fields to include in the serialization (all fields are included).
    """

    amenities = serializers.ListField(child=serializers.IntegerField(), write_only=True)
    connectors = serializers.ListField(child=serializers.DictField(), write_only=True)
    photos = serializers.ListField(child=serializers.DictField(), write_only=True)
    coordinates = PointField(required=False)

    class Meta:
        model = station_model.StationMaster
        fields = [
            "scarp_id",
            "name",
            "address",
            "description",
            "title_description",
            "meta_description",
            "phone_number",
            "is_active",
            "is_24_hours_open",
            "is_parking_free",
            "parking_cost_description",
            "coordinates",
            "amenities",
            "connectors",
            "photos",
        ]

    def create(self, validated_data):
        request_user = self.context.get("user", None)

        # Extract and remove amenities and stations data from validated_data
        amenities_data = validated_data.pop("amenities", [])
        connectors_data = validated_data.pop("connectors", [])
        photo_data = validated_data.pop("photos", [])

        amenities_ids = []

        # Check every amenity with type_id
        for amenities_type_id in amenities_data:
            try:
                amenity = AmenitiesMaster.objects.get(type_id=amenities_type_id)
                amenities_ids.append(amenity)
            except AmenitiesMaster.DoesNotExist:
                pass

        connector_data_list = []
        # Check vehicle data and create or get one.
        for connectors in connectors_data:
            network_operator = connectors.get("make")
            if connectors.get("make"):
                network_operator = get_or_create_network_operator(connectors["make"])
            if connectors.get("outlets"):
                outlets = connectors.get("outlets")
                for outlet in outlets:
                    connector_dict = {}
                    if network_operator:
                        connector_dict["network_operator"] = network_operator.id
                    else:
                        connector_dict["network_operator"] = None
                    connector = get_or_create_connector(outlet)
                    connector_dict["connector"] = connector.id
                    connector_dict["kilowatts"] = outlet["kilowatts"]
                    connector_data_list.append(connector_dict)

        scarp_id = validated_data.get("scarp_id")

        if scarp_id:
            # Try to get an existing station by scarp_id or create a new one
            station = station_model.StationMaster.objects.filter(
                scarp_id=scarp_id
            ).first()

            if station:
                return station
            else:
                station = station_services.handle_stations(scarp_id, validated_data)

            # Create or update StationAmenities and StationConnectors records
            station_services.handle_amenities(station, amenities_ids)

            # Validate and station connectors
            for connector_dict in connector_data_list:
                connector_dict["station"] = station.id
            station_connector_serializer = StationConnectorSerializer(
                data=connector_data_list,
                many=True,
            )
            if station_connector_serializer.is_valid():
                station_connector_serializer.save()
            else:
                raise serializers.ValidationError(station_connector_serializer.errors)

            # Save station photos
            station_services.handle_station_media(station, request_user, photo_data)
            return station
        else:
            raise serializers.ValidationError(
                "scarp_id is required for update or creation."
            )


class StationMasterSerializer(DynamicFieldsModelSerializer):
    """
    This is the StationMasterSerializer class responsible for serializing station data.

    It takes data from the StationMaster model and structures it for use in API responses.

    Attributes:
        coordinates (PointField): A GeoDjango PointField allowing the inclusion of location data.
        connectors (SerializerMethodField): A dynamically generated field for retrieving and
        serializing station connectors.
        amenities (SerializerMethodField): A dynamically generated field for retrieving and
        serializing station amenities.
        locations (SerializerMethodField): A dynamically generated field for retrieving and
        serializing station locations.

    Meta:
        model (StationMaster): The model used for serialization.
        fields (tuple): Specifies the fields to be included in the serialization. All fields are included,
                        including 'id', 'name', 'address', 'description', 'title_description', 'meta_description',
                        'phone_number', 'email', 'is_active', 'open_time', 'close_time', 'is_24_hours_open',
                        'is_parking_free', 'parking_cost_description', 'coordinates', and 'connectors'.

    Methods:
        get_connectors(obj): Retrieves and serializes the station connectors associated with the station.
        get_amenities(obj): Retrieves and serializes the station amenities associated with the station.
    """

    # Include a GeoDjango PointField for 'coordinates', allowing for location data.
    coordinates = PointField(required=False)
    connectors = serializers.SerializerMethodField()
    amenities = serializers.SerializerMethodField()
    locations = serializers.SerializerMethodField()
    assets = serializers.SerializerMethodField(default=[])
    rating = serializers.SerializerMethodField()
    bookmark = serializers.SerializerMethodField()
    all_network_operators = serializers.SerializerMethodField(
        read_only=True, default=[]
    )
    all_connectors = serializers.SerializerMethodField(read_only=True, default=[])
    all_locations = serializers.SerializerMethodField(read_only=True, default=[])

    class Meta:
        model = station_model.StationMaster
        fields = (
            "id",
            "name",
            "address",
            "description",
            "title_description",
            "meta_description",
            "phone_number",
            "email",
            "website",
            "is_active",
            "open_time",
            "close_time",
            "is_24_hours_open",
            "is_parking_free",
            "parking_cost_description",
            "coordinates",
            "connectors",
            "amenities",
            "locations",
            "assets",
            "rating",
            "bookmark",
            "all_network_operators",
            "all_connectors",
            "all_locations",
            "slug",
        )
        read_only_fields = fields

    def get_connectors(self, obj):
        connectors = obj.station_connectors.all()
        connectors_serializer = StationConnectorSerializer(
            connectors,
            many=True,
            fields=[
                "id",
                "kilowatts",
                "cost_description",
                "connector_data",
                "network_operator",
            ],
            context=self.context,
        )
        return connectors_serializer.data

    def get_amenities(self, obj):
        amenities = obj.station_amenities.all()
        amenities_serializer = StationAmenitiesSerializer(
            amenities, many=True, fields=["amenities"]
        )
        return amenities_serializer.data

    def get_locations(self, obj):
        locations = obj.station_locations.all()
        locations_serializer = StationLocationsSerializer(
            locations, many=True, fields=["locations"]
        )
        return locations_serializer.data

    def get_all_locations(self, obj):
        # Get all location names based on station
        locations = obj.station_locations.all()
        unique_location = set()
        result = []

        for location in locations:
            if location.location and location.location.name is not None:
                location_id = location.location.id
                if location_id not in unique_location:
                    unique_location.add(location_id)
                    result.append(location.location.name)

        return result

    def get_assets(self, obj):
        # Get station media based on station id
        station_media_assets = obj.station_medias.filter(
            station=obj,
            is_verified=True,
        ).values("asset", "asset_type", "id")

        # Get Station rating media based on station id
        station_rating_media_assets = station_model.StationRatingMedia.objects.filter(
            rating_station__station=obj,
            is_verified=True,
        ).values("asset", "asset_type", "id")
        combined_assets = station_media_assets.union(station_rating_media_assets)

        # Iterate over the results and update the 'asset' field to contain the full URL
        for asset_info in combined_assets:
            asset_info["asset"] = self.context["request"].build_absolute_uri(
                f"{settings.MEDIA_URL}{asset_info['asset']}"
            )

        return combined_assets

    def get_rating(self, obj):
        # Get total count of checkins for station
        total_count = obj.station_ratings.count()

        # Get average rating for station
        average_rating = obj.station_ratings.aggregate(Avg("rating"))["rating__avg"]

        return {
            "total_count": total_count,
            "average_rating": round(average_rating or 0, 2),
        }

    def get_bookmark(self, obj):
        # Get current request
        request = self.context.get("request", None)

        # Check request have user
        if request and request.user and not isinstance(request.user, AnonymousUser):
            # Get bookmark station by user and current station obj
            bookmark = station_model.BookmarkStation.objects.filter(
                station=obj, user=request.user
            )
            return bookmark.exists()
        return False

    def get_all_network_operators(self, obj):
        station_connectors = obj.station_connectors.all()

        unique_network_operators = set()
        result = []

        for connector in station_connectors:
            if (
                connector.network_operator
                and connector.network_operator.name is not None
            ):
                network_operator_id = connector.network_operator.id
                if network_operator_id not in unique_network_operators:
                    unique_network_operators.add(network_operator_id)
                    result.append(connector.network_operator.name)

        return result

    def get_all_connectors(self, obj):
        # Fetch all connectors for the given station's connectors
        station_connectors = obj.station_connectors.all()

        unique_connectors = set()
        result = []

        for connector in station_connectors:
            if connector.connector and connector.connector.short_name is not None:
                connector_id = connector.connector.id
                if connector_id not in unique_connectors:
                    unique_connectors.add(connector_id)
                    result.append(connector.connector.short_name)

        return result


class StationCreateUpdateMasterSerializer(DynamicFieldsModelSerializer):
    """
    This is the StationCreateUpdateMasterSerializer class responsible for serializing and deserializing station data
    when creating or updating station records.

    It handles both station details and associated amenities and connectors.

    Attributes:
        amenities (PrimaryKeyRelatedField): A field for specifying associated amenities related to the station.
        stations (StationConnectorSerializer): A field for specifying station connectors.
        locations (PrimaryKeyRelatedField): A field for specifying associated locations related to the station.
        coordinates (PointField): A GeoDjango PointField for specifying the station's geographical coordinates.

    Meta:
        model (StationMaster): The model used for serialization and deserialization.
        fields (tuple): Specifies the fields included in the serialization and deserialization process.
        read_only_fields (tuple): Fields that are marked as read-only.
        extra_kwargs (dict): Additional field-specific options and requirements.

    Methods:
        create(validated_data): Handles the creation of a new station record with associated amenities and connectors.
        update(instance, validated_data): Handles the update of an existing station record with associated amenities
        and connectors.
    """

    amenities = serializers.PrimaryKeyRelatedField(
        queryset=AmenitiesMaster.objects.all(),
        many=True,
        required=True,
        write_only=True,
    )
    stations = StationConnectorSerializer(many=True, required=True, write_only=True)
    locations = serializers.PrimaryKeyRelatedField(
        queryset=LocationMaster.objects.all(), many=True, required=True, write_only=True
    )
    coordinates = PointField(
        required=True,
    )

    class Meta:
        model = station_model.StationMaster
        fields = (
            "id",
            "name",
            "address",
            "is_owner_or_user",
            "is_parking_free",
            "parking_cost_description",
            "phone_number",
            "email",
            "website",
            "description",
            "is_24_hours_open",
            "open_time",
            "close_time",
            "is_active",
            "amenities",
            "stations",
            "coordinates",
            "locations",
            "slug",
        )
        read_only_fields = ("id", "slug")
        extra_kwargs = {
            "name": {"required": True},
            "address": {"required": True},
            "is_owner_or_user": {"required": True},
            "is_parking_free": {"required": True},
            "is_active": {"required": True},
        }

    def create(self, validated_data):
        """
        Creates a new station record and manages associated amenities and connectors.

        Args:
            validated_data (dict): Validated data containing station details, amenities, connectors, and coordinates.

        Returns:
            StationMaster: The newly created station record.

        Raises:
            serializers.ValidationError: If the 'connectors' data is empty, indicating an incomplete station creation.
        ```
        """

        # Extract amenities, stations and locations data from validated_data
        amenities_data = validated_data.pop("amenities", [])
        connectors_data = validated_data.pop("stations", [])
        locations_data = validated_data.pop("locations", [])

        # Creating connector data dict
        connectors_dict_data = []
        for connector in connectors_data:
            network_operator = connector.get("network_operator")
            connector_dict = {}
            if network_operator:
                connector_dict["network_operator"] = network_operator.id
            else:
                connector_dict["network_operator"] = None
            connector_dict["connector"] = connector["connector"].id
            connector_dict["kilowatts"] = connector.get("kilowatts")
            connectors_dict_data.append(connector_dict)

        # Extract request from context
        request = self.context.get("request")

        if len(connectors_data) == 0:
            raise serializers.ValidationError("Stations can not be empty")

        # Try to get an existing station by scarp_id or create a new one
        if request.user.is_superuser:
            validated_data["is_verified"] = True
        station = station_services.handle_stations(validated_data=validated_data)

        # Create or update StationAmenities, StationConnectors and StationLocations records
        station_services.handle_stations_user(station, request.user)
        station_services.handle_locations(station, locations_data)
        station_services.handle_amenities(station, amenities_data)

        # Validate and save Station Connector data
        station_connector_serializer = StationConnectorSerializer(
            data=connectors_dict_data, many=True
        )
        station_connector_serializer.is_valid(raise_exception=True)
        station_connector_serializer.save(station=station)

        return station

    def update(self, instance, validated_data):
        # Extract amenities, stations and locations data from validated_data
        amenities_data = validated_data.pop("amenities", [])
        connectors_data = validated_data.pop("stations", [])
        locations_data = validated_data.pop("locations", [])

        # Set value for current update station instance
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()

        # Create or update StationAmenities, StationConnectors and StationLocations records
        station_services.handle_locations(instance, locations_data)
        station_services.handle_amenities(instance, amenities_data)
        station_services.handle_update_connectors(instance, connectors_data)

        return instance


class UploadStationMediaSerializer(DynamicFieldsModelSerializer):
    """
    This is the UploadStationMediaSerializer class responsible for serializing and deserializing station media assets.

    It allows users to manage the upload of media assets, associating them with station records.

    Meta:
        model (StationMedia): The model used for serialization and deserialization.
        fields (tuple): Specifies the fields included in the serialization and deserialization process.
        read_only_fields (tuple): Fields that are marked as read-only.
    """

    class Meta:
        model = station_model.StationMedia
        fields = ("id", "asset", "asset_type", "is_verified")
        read_only_fields = ("id",)


class StationVerificationSerializer(DynamicFieldsModelSerializer):
    """
    Serializer for verifying station details and media uploads.

    This serializer is responsible for serializing and deserializing station media assets
    for the purpose of verifying station information and uploading media files associated with stations.

    Attributes:
        coordinates (PointField): Represents the geographical coordinates of the station's location.
        It is a required field for verifying the station.

    Meta:
        model (StationMaster): Specifies the model used for serialization and deserialization,
        which in this case is 'StationMaster'.
        fields (tuple): Specifies the fields included in the serialization and deserialization process.
        These fields include 'id', 'name', 'address', 'description', 'phone_number', 'email',
        'website', 'is_active', 'open_time', 'close_time', 'is_24_hours_open', 'is_parking_free',
        'parking_cost_description', 'coordinates', and 'is_verified'.
        read_only_fields (tuple): Defines fields that are marked as read-only, and in this case,
        'id' is read-only.
    """

    coordinates = PointField(
        required=True,
    )
    assets = serializers.SerializerMethodField()

    class Meta:
        model = station_model.StationMaster
        fields = (
            "id",
            "name",
            "address",
            "description",
            "phone_number",
            "email",
            "website",
            "is_active",
            "open_time",
            "close_time",
            "is_24_hours_open",
            "is_parking_free",
            "parking_cost_description",
            "coordinates",
            "is_verified",
            "assets",
        )
        read_only_fields = ("id",)

    def get_assets(self, obj):
        # Get station media based on station id
        assets = []

        # Iterate over the results and update the 'assets' field to contain the full URL
        for station_assets in obj.station_medias.all():
            assets.append(
                {
                    "id": station_assets.id,
                    "asset": self.context["request"].build_absolute_uri(
                        f"{settings.MEDIA_URL}{station_assets.asset}"
                    ),
                }
            )

        return assets


class CombinedStationAssetSerializer(DynamicFieldsSerializer):
    """
    Serializer for combining station media and rating media assets with additional information.

    Attributes:
        id (int): The identifier for the combined asset.
        asset (str): The full URL of the asset.
        asset_type (int): The type of the asset.

    Methods:
        get_asset(obj): Method to retrieve the full URL of the asset.
    """

    id = serializers.IntegerField()
    asset = serializers.SerializerMethodField()
    asset_type = serializers.IntegerField()

    def get_asset(self, obj):
        """
        Retrieve the full URL of the asset.

        Args:
            obj (dict): The dictionary representing the combined asset.

        Returns:
            str or None: The full URL of the asset or None if it's not available.
        """
        request = self.context.get("request")
        if obj.get("asset"):
            return request.build_absolute_uri(obj["asset"])
        return None
