from decimal import Decimal

from django.db.models import Sum
from rest_framework import serializers

from apps.workrecords.services import sync_delivery_work_record
from .models import (
    DeliveryGroup,
    DeliveryGroupField,
    DeliveryPlan,
    DeliveryTrip,
    DeliveryTripItem,
    FertilizationEntry,
    FertilizationPlan,
    Fertilizer,
    SpreadingSession,
    SpreadingSessionItem,
)
from .services import sync_actual_bags_for_pairs, sync_spreading_session_side_effects


class FertilizerSerializer(serializers.ModelSerializer):
    material_id = serializers.SerializerMethodField()

    class Meta:
        model = Fertilizer
        fields = [
            'id',
            'name',
            'maker',
            'capacity_kg',
            'nitrogen_pct',
            'phosphorus_pct',
            'potassium_pct',
            'notes',
            'material',
            'material_id',
        ]

    def get_material_id(self, obj):
        return obj.material_id


class FertilizationEntrySerializer(serializers.ModelSerializer):
    field_name = serializers.CharField(source='field.name', read_only=True)
    field_area_tan = serializers.DecimalField(
        source='field.area_tan', max_digits=6, decimal_places=4, read_only=True
    )
    fertilizer_name = serializers.CharField(source='fertilizer.name', read_only=True)

    class Meta:
        model = FertilizationEntry
        fields = [
            'id',
            'field',
            'field_name',
            'field_area_tan',
            'fertilizer',
            'fertilizer_name',
            'bags',
            'actual_bags',
        ]


class FertilizationPlanSerializer(serializers.ModelSerializer):
    variety_name = serializers.SerializerMethodField()
    crop_name = serializers.SerializerMethodField()
    entries = FertilizationEntrySerializer(many=True, read_only=True)
    field_count = serializers.SerializerMethodField()
    fertilizer_count = serializers.SerializerMethodField()
    planned_total_bags = serializers.SerializerMethodField()
    spread_total_bags = serializers.SerializerMethodField()
    remaining_total_bags = serializers.SerializerMethodField()
    spread_status = serializers.SerializerMethodField()
    is_confirmed = serializers.BooleanField(read_only=True)
    confirmed_at = serializers.DateTimeField(read_only=True)
    is_variety_change_plan = serializers.SerializerMethodField()

    class Meta:
        model = FertilizationPlan
        fields = [
            'id',
            'name',
            'year',
            'variety',
            'variety_name',
            'crop_name',
            'calc_settings',
            'entries',
            'field_count',
            'fertilizer_count',
            'planned_total_bags',
            'spread_total_bags',
            'remaining_total_bags',
            'spread_status',
            'is_confirmed',
            'confirmed_at',
            'is_variety_change_plan',
            'created_at',
            'updated_at',
        ]

    def get_variety_name(self, obj):
        return obj.variety.name

    def get_crop_name(self, obj):
        return obj.variety.crop.name

    def get_field_count(self, obj):
        return obj.entries.values('field').distinct().count()

    def get_fertilizer_count(self, obj):
        return obj.entries.values('fertilizer').distinct().count()

    def get_planned_total_bags(self, obj):
        total = sum((entry.bags or Decimal('0')) for entry in obj.entries.all())
        return str(total)

    def get_spread_total_bags(self, obj):
        total = sum((entry.actual_bags or Decimal('0')) for entry in obj.entries.all())
        return str(total)

    def get_remaining_total_bags(self, obj):
        planned = sum((entry.bags or Decimal('0')) for entry in obj.entries.all())
        actual = sum((entry.actual_bags or Decimal('0')) for entry in obj.entries.all())
        return str(planned - actual)

    def get_spread_status(self, obj):
        planned = sum((entry.bags or Decimal('0')) for entry in obj.entries.all())
        actual = sum((entry.actual_bags or Decimal('0')) for entry in obj.entries.all())
        if actual <= 0:
            return 'unspread'
        if actual > planned:
            return 'over_applied'
        if actual < planned:
            return 'partial'
        return 'completed'

    def get_is_variety_change_plan(self, obj):
        return obj.name.endswith('（品種変更移動）')


class FertilizationPlanWriteSerializer(serializers.ModelSerializer):
    entries = serializers.ListField(child=serializers.DictField(), write_only=True, required=False)

    class Meta:
        model = FertilizationPlan
        fields = ['id', 'name', 'year', 'variety', 'calc_settings', 'entries']

    def create(self, validated_data):
        entries_data = validated_data.pop('entries', [])
        plan = FertilizationPlan.objects.create(**validated_data)
        pairs = self._save_entries(plan, entries_data)
        sync_actual_bags_for_pairs(plan.year, pairs)
        return plan

    def update(self, instance, validated_data):
        entries_data = validated_data.pop('entries', None)
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        if entries_data is not None:
            instance.entries.all().delete()
            pairs = self._save_entries(instance, entries_data)
            sync_actual_bags_for_pairs(instance.year, pairs)
        return instance

    def _save_entries(self, plan, entries_data):
        pairs = set()
        for entry in entries_data:
            pairs.add((entry['field_id'], entry['fertilizer_id']))
            FertilizationEntry.objects.create(
                plan=plan,
                field_id=entry['field_id'],
                fertilizer_id=entry['fertilizer_id'],
                bags=entry['bags'],
            )
        return pairs


class DeliveryGroupFieldSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(source='field.id', read_only=True)
    name = serializers.CharField(source='field.name', read_only=True)
    area_tan = serializers.DecimalField(
        source='field.area_tan', max_digits=6, decimal_places=4, read_only=True
    )

    class Meta:
        model = DeliveryGroupField
        fields = ['id', 'name', 'area_tan']


class DeliveryGroupReadSerializer(serializers.ModelSerializer):
    fields = DeliveryGroupFieldSerializer(source='field_assignments', many=True, read_only=True)

    class Meta:
        model = DeliveryGroup
        fields = ['id', 'name', 'order', 'fields']


class DeliveryTripItemSerializer(serializers.ModelSerializer):
    field_name = serializers.CharField(source='field.name', read_only=True)
    fertilizer_name = serializers.CharField(source='fertilizer.name', read_only=True)
    spread_bags = serializers.SerializerMethodField()
    remaining_bags = serializers.SerializerMethodField()

    class Meta:
        model = DeliveryTripItem
        fields = [
            'id',
            'field',
            'field_name',
            'fertilizer',
            'fertilizer_name',
            'bags',
            'spread_bags',
            'remaining_bags',
        ]

    def get_spread_bags(self, obj):
        total = (
            SpreadingSessionItem.objects.filter(
                session__year=obj.trip.delivery_plan.year,
                field_id=obj.field_id,
                fertilizer_id=obj.fertilizer_id,
            ).aggregate(total=Sum('actual_bags'))['total']
        )
        return str(total or Decimal('0'))

    def get_remaining_bags(self, obj):
        total = (
            SpreadingSessionItem.objects.filter(
                session__year=obj.trip.delivery_plan.year,
                field_id=obj.field_id,
                fertilizer_id=obj.fertilizer_id,
            ).aggregate(total=Sum('actual_bags'))['total']
        )
        spread_total = total or Decimal('0')
        return str(obj.bags - spread_total)


class DeliveryTripReadSerializer(serializers.ModelSerializer):
    items = DeliveryTripItemSerializer(many=True, read_only=True)
    work_record_id = serializers.IntegerField(source='work_record.id', read_only=True)

    class Meta:
        model = DeliveryTrip
        fields = ['id', 'order', 'name', 'date', 'work_record_id', 'items']


class DeliveryPlanListSerializer(serializers.ModelSerializer):
    group_count = serializers.SerializerMethodField()
    trip_count = serializers.SerializerMethodField()

    class Meta:
        model = DeliveryPlan
        fields = [
            'id',
            'year',
            'name',
            'group_count',
            'trip_count',
            'created_at',
            'updated_at',
        ]

    def get_group_count(self, obj):
        return obj.groups.count()

    def get_trip_count(self, obj):
        return obj.trips.count()


class DeliveryPlanReadSerializer(serializers.ModelSerializer):
    groups = DeliveryGroupReadSerializer(many=True, read_only=True)
    trips = DeliveryTripReadSerializer(many=True, read_only=True)
    unassigned_fields = serializers.SerializerMethodField()
    available_fertilizers = serializers.SerializerMethodField()
    all_entries = serializers.SerializerMethodField()

    class Meta:
        model = DeliveryPlan
        fields = [
            'id',
            'year',
            'name',
            'groups',
            'trips',
            'unassigned_fields',
            'available_fertilizers',
            'all_entries',
            'created_at',
            'updated_at',
        ]

    def get_unassigned_fields(self, obj):
        assigned_ids = DeliveryGroupField.objects.filter(
            delivery_plan=obj
        ).values_list('field_id', flat=True)
        plan_field_ids = FertilizationEntry.objects.filter(
            plan__year=obj.year
        ).values_list('field_id', flat=True).distinct()
        from apps.fields.models import Field

        unassigned = Field.objects.filter(
            id__in=plan_field_ids
        ).exclude(id__in=assigned_ids).order_by('display_order', 'id')
        return [{'id': f.id, 'name': f.name, 'area_tan': str(f.area_tan)} for f in unassigned]

    def get_available_fertilizers(self, obj):
        fert_ids = FertilizationEntry.objects.filter(
            plan__year=obj.year
        ).values_list('fertilizer_id', flat=True).distinct()
        fertilizers = Fertilizer.objects.filter(id__in=fert_ids).order_by('name')
        return [{'id': f.id, 'name': f.name} for f in fertilizers]

    def get_all_entries(self, obj):
        entries = FertilizationEntry.objects.filter(
            plan__year=obj.year
        ).select_related('field', 'fertilizer')
        return [
            {
                'field': entry.field_id,
                'field_name': entry.field.name,
                'field_area_tan': str(entry.field.area_tan),
                'fertilizer': entry.fertilizer_id,
                'fertilizer_name': entry.fertilizer.name,
                'bags': str(entry.bags),
                'actual_bags': str(entry.actual_bags) if entry.actual_bags is not None else None,
            }
            for entry in entries
        ]


class DeliveryPlanWriteSerializer(serializers.ModelSerializer):
    groups = serializers.ListField(child=serializers.DictField(), write_only=True, required=False)
    trips = serializers.ListField(child=serializers.DictField(), write_only=True, required=False)

    class Meta:
        model = DeliveryPlan
        fields = ['id', 'year', 'name', 'groups', 'trips']

    def create(self, validated_data):
        groups_data = validated_data.pop('groups', [])
        trips_data = validated_data.pop('trips', [])
        plan = DeliveryPlan.objects.create(**validated_data)
        self._save_groups(plan, groups_data)
        self._save_trips(plan, trips_data)
        return plan

    def update(self, instance, validated_data):
        groups_data = validated_data.pop('groups', None)
        trips_data = validated_data.pop('trips', None)
        instance.name = validated_data.get('name', instance.name)
        instance.year = validated_data.get('year', instance.year)
        instance.save()
        if groups_data is not None:
            instance.groups.all().delete()
            self._save_groups(instance, groups_data)
        if trips_data is not None:
            instance.trips.all().delete()
            self._save_trips(instance, trips_data)
        return instance

    def _save_groups(self, plan, groups_data):
        for group_data in groups_data:
            group = DeliveryGroup.objects.create(
                delivery_plan=plan,
                name=group_data['name'],
                order=group_data.get('order', 0),
            )
            for field_id in group_data.get('field_ids', []):
                DeliveryGroupField.objects.create(
                    delivery_plan=plan,
                    group=group,
                    field_id=field_id,
                )

    def _save_trips(self, plan, trips_data):
        for trip_data in trips_data:
            trip = DeliveryTrip.objects.create(
                delivery_plan=plan,
                order=trip_data.get('order', 0),
                name=trip_data.get('name', ''),
                date=trip_data.get('date'),
            )
            for item in trip_data.get('items', []):
                DeliveryTripItem.objects.create(
                    trip=trip,
                    field_id=item['field_id'],
                    fertilizer_id=item['fertilizer_id'],
                    bags=item['bags'],
                )
            sync_delivery_work_record(trip)


class SpreadingSessionItemReadSerializer(serializers.ModelSerializer):
    field_name = serializers.CharField(source='field.name', read_only=True)
    fertilizer_name = serializers.CharField(source='fertilizer.name', read_only=True)

    class Meta:
        model = SpreadingSessionItem
        fields = [
            'id',
            'field',
            'field_name',
            'fertilizer',
            'fertilizer_name',
            'actual_bags',
            'planned_bags_snapshot',
            'delivered_bags_snapshot',
        ]


class SpreadingSessionSerializer(serializers.ModelSerializer):
    items = SpreadingSessionItemReadSerializer(many=True, read_only=True)
    work_record_id = serializers.IntegerField(source='work_record.id', read_only=True)

    class Meta:
        model = SpreadingSession
        fields = [
            'id',
            'year',
            'date',
            'name',
            'notes',
            'work_record_id',
            'items',
            'created_at',
            'updated_at',
        ]


class SpreadingSessionItemWriteInputSerializer(serializers.Serializer):
    field_id = serializers.IntegerField()
    fertilizer_id = serializers.IntegerField()
    actual_bags = serializers.DecimalField(max_digits=10, decimal_places=4)
    planned_bags_snapshot = serializers.DecimalField(max_digits=10, decimal_places=4)
    delivered_bags_snapshot = serializers.DecimalField(max_digits=10, decimal_places=4)


class SpreadingSessionWriteSerializer(serializers.ModelSerializer):
    items = SpreadingSessionItemWriteInputSerializer(many=True, write_only=True)

    class Meta:
        model = SpreadingSession
        fields = ['id', 'year', 'date', 'name', 'notes', 'items']

    def validate_items(self, value):
        if not value:
            raise serializers.ValidationError('items を1件以上指定してください。')
        seen = set()
        for item in value:
            if item['actual_bags'] <= 0:
                raise serializers.ValidationError('actual_bags は 0 より大きい値を指定してください。')
            key = (item['field_id'], item['fertilizer_id'])
            if key in seen:
                raise serializers.ValidationError('同一 session 内で field + fertilizer を重複登録できません。')
            seen.add(key)
        return value

    def create(self, validated_data):
        items_data = validated_data.pop('items', [])
        session = SpreadingSession.objects.create(**validated_data)
        new_pairs = self._replace_items(session, items_data)
        sync_spreading_session_side_effects(session, new_pairs)
        return session

    def update(self, instance, validated_data):
        items_data = validated_data.pop('items', [])
        old_pairs = {(item.field_id, item.fertilizer_id) for item in instance.items.all()}
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        new_pairs = self._replace_items(instance, items_data)
        sync_spreading_session_side_effects(instance, old_pairs | new_pairs)
        return instance

    def _replace_items(self, session, items_data):
        session.items.all().delete()
        new_pairs = set()
        for item in items_data:
            new_pairs.add((item['field_id'], item['fertilizer_id']))
            SpreadingSessionItem.objects.create(
                session=session,
                field_id=item['field_id'],
                fertilizer_id=item['fertilizer_id'],
                actual_bags=item['actual_bags'],
                planned_bags_snapshot=item['planned_bags_snapshot'],
                delivered_bags_snapshot=item['delivered_bags_snapshot'],
            )
        return new_pairs
