from decimal import Decimal, InvalidOperation

from django.db.models import Sum
from django.http import HttpResponse
from django.template.loader import render_to_string
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from weasyprint import HTML

from apps.fields.models import Field
from apps.materials.stock_service import (
    create_reserves_for_plan,
    delete_reserves_for_plan,
)
from apps.plans.models import Plan
from .models import (
    Fertilizer, FertilizationPlan, FertilizationEntry,
    DeliveryPlan, DeliveryGroup, DeliveryGroupField, DeliveryTrip, DeliveryTripItem,
    SpreadingSession, SpreadingSessionItem,
)
from .serializers import (
    FertilizerSerializer,
    FertilizationPlanSerializer,
    FertilizationPlanWriteSerializer,
    DeliveryPlanListSerializer,
    DeliveryPlanReadSerializer,
    DeliveryPlanWriteSerializer,
    SpreadingSessionSerializer,
    SpreadingSessionWriteSerializer,
)
from .services import (
    FertilizationPlanMergeConflict,
    FertilizationPlanMergeError,
    merge_fertilization_plan_into,
    sync_actual_bags_for_pairs,
)


class FertilizerViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    queryset = Fertilizer.objects.all()
    serializer_class = FertilizerSerializer


class FertilizationPlanViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        qs = FertilizationPlan.objects.select_related('variety', 'variety__crop').prefetch_related(
            'entries', 'entries__field', 'entries__fertilizer', 'entries__fertilizer__material'
        )
        year = self.request.query_params.get('year')
        if year:
            qs = qs.filter(year=year)
        return qs

    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            return FertilizationPlanWriteSerializer
        return FertilizationPlanSerializer

    def perform_create(self, serializer):
        instance = serializer.save()
        create_reserves_for_plan(instance)

    def perform_update(self, serializer):
        instance = serializer.save()
        create_reserves_for_plan(instance)

    def perform_destroy(self, instance):
        delete_reserves_for_plan(instance)
        instance.delete()

    @action(detail=True, methods=['get'])
    def pdf(self, request, pk=None):
        plan = self.get_object()
        entries = plan.entries.select_related('field', 'fertilizer').order_by(
            'field__display_order', 'field__id', 'fertilizer__name'
        )

        # 圃場・肥料の一覧を整理
        fields_map = {}
        fertilizers_map = {}
        for entry in entries:
            fields_map[entry.field_id] = entry.field
            fertilizers_map[entry.fertilizer_id] = entry.fertilizer

        fields = sorted(fields_map.values(), key=lambda f: (f.display_order, f.id))
        fertilizers = sorted(fertilizers_map.values(), key=lambda f: f.name)

        # マトリクスデータ生成
        matrix = {}
        for entry in entries:
            matrix[(entry.field_id, entry.fertilizer_id)] = entry.bags

        rows = []
        for field in fields:
            cells = [matrix.get((field.id, fert.id), '') for fert in fertilizers]
            total = sum(v for v in cells if v != '')
            rows.append({
                'field': field,
                'cells': cells,
                'total': total,
            })

        # 肥料ごとの合計
        fert_totals = []
        for fert in fertilizers:
            total = sum(
                matrix.get((field.id, fert.id), Decimal('0'))
                for field in fields
            )
            fert_totals.append(total)

        context = {
            'plan': plan,
            'fertilizers': fertilizers,
            'rows': rows,
            'fert_totals': fert_totals,
            'grand_total': sum(fert_totals),
        }
        html_string = render_to_string('fertilizer/pdf.html', context)
        pdf_file = HTML(string=html_string).write_pdf()
        response = HttpResponse(pdf_file, content_type='application/pdf')
        response['Content-Disposition'] = f'attachment; filename="fertilization_{plan.year}_{plan.id}.pdf"'
        return response

    @action(detail=True, methods=['get'])
    def merge_targets(self, request, pk=None):
        source_plan = self.get_object()
        targets = (
            FertilizationPlan.objects
            .filter(year=source_plan.year, variety_id=source_plan.variety_id)
            .exclude(id=source_plan.id)
            .prefetch_related('entries')
            .order_by('-updated_at', 'id')
        )
        data = [
            {
                'id': plan.id,
                'name': plan.name,
                'field_count': plan.entries.values('field').distinct().count(),
                'planned_total_bags': str(sum((entry.bags or Decimal('0')) for entry in plan.entries.all())),
                'is_confirmed': plan.is_confirmed,
            }
            for plan in targets
        ]
        return Response(data)

    @action(detail=True, methods=['post'])
    def merge_into(self, request, pk=None):
        source_plan = self.get_object()
        target_plan_id = request.data.get('target_plan_id')
        if not target_plan_id:
            return Response({'error': 'target_plan_id が必要です。'}, status=status.HTTP_400_BAD_REQUEST)

        try:
            target_plan = FertilizationPlan.objects.get(id=target_plan_id)
        except FertilizationPlan.DoesNotExist:
            return Response({'error': 'マージ先の施肥計画が見つかりません。'}, status=status.HTTP_404_NOT_FOUND)

        try:
            result = merge_fertilization_plan_into(source_plan, target_plan)
        except FertilizationPlanMergeConflict as exc:
            return Response(
                {
                    'error': '競合する圃場・肥料があるためマージできません。',
                    'conflicts': exc.conflicts,
                },
                status=status.HTTP_409_CONFLICT,
            )
        except FertilizationPlanMergeError as exc:
            return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)

        return Response(result)

class CandidateFieldsView(APIView):
    """作付け計画から圃場候補を返す"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        year = request.query_params.get('year')
        variety_id = request.query_params.get('variety_id')
        if not year or not variety_id:
            return Response({'error': 'year と variety_id が必要です'}, status=status.HTTP_400_BAD_REQUEST)

        field_ids = Plan.objects.filter(
            year=year, variety_id=variety_id
        ).values_list('field_id', flat=True)

        fields = Field.objects.filter(id__in=field_ids).order_by('display_order', 'id')
        data = [
            {
                'id': f.id,
                'name': f.name,
                'area_tan': str(f.area_tan),
                'area_m2': f.area_m2,
                'group_name': f.group_name,
            }
            for f in fields
        ]
        return Response(data)


class CalculateView(APIView):
    """自動計算（保存しない）"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        method = request.data.get('method')   # 'nitrogen' | 'even' | 'per_tan'
        param = request.data.get('param')     # 数値パラメータ
        fertilizer_id = request.data.get('fertilizer_id')
        field_ids = request.data.get('field_ids', [])

        if not method or param is None or not field_ids:
            return Response({'error': 'method, param, field_ids が必要です'}, status=status.HTTP_400_BAD_REQUEST)

        try:
            param = Decimal(str(param))
        except InvalidOperation:
            return Response({'error': 'param は数値で指定してください'}, status=status.HTTP_400_BAD_REQUEST)

        fields = Field.objects.filter(id__in=field_ids)
        if not fields.exists():
            return Response({'error': '圃場が見つかりません'}, status=status.HTTP_400_BAD_REQUEST)

        results = []

        if method == 'per_tan':
            # 反当袋数配分: S = Sa × A
            for field in fields:
                area = Decimal(str(field.area_tan))
                bags = (param * area).quantize(Decimal('0.01'))
                results.append({'field_id': field.id, 'bags': float(bags)})

        elif method == 'even':
            # 在庫/指定数量均等配分: S = (SS / Sum(A)) × A
            total_area = sum(Decimal(str(f.area_tan)) for f in fields)
            if total_area == 0:
                return Response({'error': '圃場の面積が0です'}, status=status.HTTP_400_BAD_REQUEST)
            for field in fields:
                area = Decimal(str(field.area_tan))
                bags = (param * area / total_area).quantize(Decimal('0.01'))
                results.append({'field_id': field.id, 'bags': float(bags)})

        elif method == 'nitrogen':
            # 反当チッソ成分量配分: S = (Nr / (C × Nd/100)) × A
            if not fertilizer_id:
                return Response({'error': 'nitrogen 方式には fertilizer_id が必要です'}, status=status.HTTP_400_BAD_REQUEST)
            try:
                fertilizer = Fertilizer.objects.get(id=fertilizer_id)
            except Fertilizer.DoesNotExist:
                return Response({'error': '肥料が見つかりません'}, status=status.HTTP_404_NOT_FOUND)
            if not fertilizer.capacity_kg or not fertilizer.nitrogen_pct:
                return Response(
                    {'error': 'この肥料には1袋重量(kg)と窒素含有率(%)の登録が必要です'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            c = Decimal(str(fertilizer.capacity_kg))
            nd = Decimal(str(fertilizer.nitrogen_pct))
            # 1袋あたりの窒素量 (kg)
            nc = c * nd / Decimal('100')
            if nc == 0:
                return Response({'error': '窒素含有量が0のため計算できません'}, status=status.HTTP_400_BAD_REQUEST)
            for field in fields:
                area = Decimal(str(field.area_tan))
                bags = (param / nc * area).quantize(Decimal('0.01'))
                results.append({'field_id': field.id, 'bags': float(bags)})

        else:
            return Response({'error': 'method は nitrogen / even / per_tan のいずれかです'}, status=status.HTTP_400_BAD_REQUEST)

        return Response(results)


class DeliveryPlanViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        qs = DeliveryPlan.objects.prefetch_related(
            'groups', 'groups__field_assignments', 'groups__field_assignments__field',
            'trips', 'trips__items', 'trips__items__field', 'trips__items__fertilizer',
        )
        year = self.request.query_params.get('year')
        if year:
            qs = qs.filter(year=year)
        return qs

    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            return DeliveryPlanWriteSerializer
        if self.action == 'list':
            return DeliveryPlanListSerializer
        return DeliveryPlanReadSerializer

    @action(detail=True, methods=['get'])
    def pdf(self, request, pk=None):
        plan = self.get_object()

        # 全tripのitemから使用肥料を収集
        all_items = DeliveryTripItem.objects.filter(
            trip__delivery_plan=plan
        ).select_related('field', 'fertilizer')

        fert_ids = all_items.values_list('fertilizer_id', flat=True).distinct()
        fertilizers = sorted(
            Fertilizer.objects.filter(id__in=fert_ids),
            key=lambda f: f.name
        )

        # グループ情報: field_id → group_name
        field_group_map = {}
        for gf in DeliveryGroupField.objects.filter(
            delivery_plan=plan
        ).select_related('group', 'field'):
            field_group_map[gf.field_id] = gf.group

        # 回ごとにページを構築
        trip_pages = []
        for trip in plan.trips.prefetch_related('items__field', 'items__fertilizer').all():
            items = trip.items.all()
            if not items:
                continue

            # この回の肥料一覧
            trip_fert_ids = set(item.fertilizer_id for item in items)
            trip_fertilizers = [f for f in fertilizers if f.id in trip_fert_ids]

            # items を (field_id, fertilizer_id) → bags のマトリクスに変換
            item_map = {}
            for item in items:
                item_map[(item.field_id, item.fertilizer_id)] = item.bags

            # グループごとにまとめる
            groups_dict = {}  # group_name → {'group': group, 'fields': [field, ...]}
            ungrouped_fields = []
            for item in items:
                group = field_group_map.get(item.field_id)
                if group:
                    if group.name not in groups_dict:
                        groups_dict[group.name] = {'group': group, 'fields': []}
                    if item.field not in groups_dict[group.name]['fields']:
                        groups_dict[group.name]['fields'].append(item.field)
                else:
                    if item.field not in ungrouped_fields:
                        ungrouped_fields.append(item.field)

            # グループを order 順にソート
            sorted_groups = sorted(groups_dict.values(), key=lambda g: (g['group'].order, g['group'].id))

            group_rows = []
            for g_data in sorted_groups:
                fields_in_group = sorted(g_data['fields'], key=lambda f: (f.display_order, f.id))
                group_totals = []
                for fert in trip_fertilizers:
                    total = sum(
                        item_map.get((f.id, fert.id), Decimal('0'))
                        for f in fields_in_group
                    )
                    group_totals.append(total)

                field_rows = []
                for field in fields_in_group:
                    cells = [item_map.get((field.id, fert.id), '') for fert in trip_fertilizers]
                    field_rows.append({'field': field, 'cells': cells})

                group_rows.append({
                    'name': g_data['group'].name,
                    'totals': group_totals,
                    'field_rows': field_rows,
                })

            # 未グループ圃場
            if ungrouped_fields:
                ungrouped_fields = sorted(ungrouped_fields, key=lambda f: (f.display_order, f.id))
                ua_totals = [
                    sum(item_map.get((f.id, fert.id), Decimal('0')) for f in ungrouped_fields)
                    for fert in trip_fertilizers
                ]
                field_rows = []
                for field in ungrouped_fields:
                    cells = [item_map.get((field.id, fert.id), '') for fert in trip_fertilizers]
                    field_rows.append({'field': field, 'cells': cells})
                group_rows.append({
                    'name': '未グループ',
                    'totals': ua_totals,
                    'field_rows': field_rows,
                })

            fert_totals = [
                sum(r['totals'][i] for r in group_rows)
                for i in range(len(trip_fertilizers))
            ]

            trip_pages.append({
                'trip': trip,
                'fertilizers': trip_fertilizers,
                'group_rows': group_rows,
                'fert_totals': fert_totals,
            })

        context = {
            'plan': plan,
            'trip_pages': trip_pages,
        }
        html_string = render_to_string('fertilizer/delivery_pdf.html', context)
        pdf_file = HTML(string=html_string).write_pdf()
        response = HttpResponse(pdf_file, content_type='application/pdf')
        response['Content-Disposition'] = (
            f'attachment; filename="delivery_{plan.year}_{plan.id}.pdf"'
        )
        return response


class SpreadingSessionViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        queryset = SpreadingSession.objects.prefetch_related(
            'items',
            'items__field',
            'items__fertilizer',
        ).select_related('work_record')
        year = self.request.query_params.get('year')
        if year:
            queryset = queryset.filter(year=year)
        return queryset

    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            return SpreadingSessionWriteSerializer
        return SpreadingSessionSerializer

    def perform_destroy(self, instance):
        from apps.materials.models import StockTransaction
        year = instance.year
        affected_pairs = {(item.field_id, item.fertilizer_id) for item in instance.items.all()}
        StockTransaction.objects.filter(spreading_item__session=instance).delete()
        instance.delete()
        sync_actual_bags_for_pairs(year, affected_pairs)


class SpreadingCandidatesView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        year = request.query_params.get('year')
        session_id = request.query_params.get('session_id')
        delivery_plan_id = request.query_params.get('delivery_plan_id')
        plan_id = request.query_params.get('plan_id')
        if not year:
            return Response(
                {'detail': 'year が必要です。'},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            year = int(year)
        except (TypeError, ValueError):
            return Response(
                {'detail': 'year は数値で指定してください。'},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if delivery_plan_id:
            try:
                delivery_plan_id = int(delivery_plan_id)
            except (TypeError, ValueError):
                return Response(
                    {'detail': 'delivery_plan_id は数値で指定してください。'},
                    status=status.HTTP_400_BAD_REQUEST,
                )
        if plan_id:
            try:
                plan_id = int(plan_id)
            except (TypeError, ValueError):
                return Response(
                    {'detail': 'plan_id は数値で指定してください。'},
                    status=status.HTTP_400_BAD_REQUEST,
                )

        current_session = None
        current_map = {}
        if session_id:
            try:
                current_session = SpreadingSession.objects.prefetch_related('items').get(
                    pk=session_id,
                    year=year,
                )
            except SpreadingSession.DoesNotExist:
                return Response(
                    {'detail': '散布実績が見つかりません。'},
                    status=status.HTTP_404_NOT_FOUND,
                )
            current_map = {
                (item.field_id, item.fertilizer_id): {
                    'actual_bags': item.actual_bags,
                    'field_name': item.field.name,
                    'field_area_tan': str(item.field.area_tan),
                    'fertilizer_name': item.fertilizer.name,
                }
                for item in current_session.items.all()
            }

        candidates = {}

        plan_queryset = FertilizationEntry.objects.filter(plan__year=year)
        if plan_id:
            plan_queryset = plan_queryset.filter(plan_id=plan_id)
        plan_rows = (
            plan_queryset
            .values(
                'field_id',
                'field__name',
                'field__area_tan',
                'fertilizer_id',
                'fertilizer__name',
            )
            .annotate(planned_bags=Sum('bags'))
        )
        for row in plan_rows:
            key = (row['field_id'], row['fertilizer_id'])
            candidates.setdefault(
                key,
                {
                    'field': row['field_id'],
                    'field_name': row['field__name'],
                    'field_area_tan': str(row['field__area_tan']),
                    'fertilizer': row['fertilizer_id'],
                    'fertilizer_name': row['fertilizer__name'],
                    'planned_bags': Decimal('0'),
                    'delivered_bags': Decimal('0'),
                    'spread_bags': Decimal('0'),
                    'current_session_bags': Decimal('0'),
                },
            )['planned_bags'] = row['planned_bags'] or Decimal('0')

        delivery_queryset = DeliveryTripItem.objects.filter(trip__delivery_plan__year=year)
        if delivery_plan_id:
            delivery_queryset = delivery_queryset.filter(trip__delivery_plan_id=delivery_plan_id)
        else:
            delivery_queryset = delivery_queryset.filter(trip__date__isnull=False)
        delivery_rows = delivery_queryset.values(
            'field_id',
            'field__name',
            'field__area_tan',
            'fertilizer_id',
            'fertilizer__name',
        ).annotate(delivered_bags=Sum('bags'))
        for row in delivery_rows:
            key = (row['field_id'], row['fertilizer_id'])
            candidates.setdefault(
                key,
                {
                    'field': row['field_id'],
                    'field_name': row['field__name'],
                    'field_area_tan': str(row['field__area_tan']),
                    'fertilizer': row['fertilizer_id'],
                    'fertilizer_name': row['fertilizer__name'],
                    'planned_bags': Decimal('0'),
                    'delivered_bags': Decimal('0'),
                    'spread_bags': Decimal('0'),
                    'current_session_bags': Decimal('0'),
                },
            )['delivered_bags'] = row['delivered_bags'] or Decimal('0')

        spread_queryset = SpreadingSessionItem.objects.filter(session__year=year)
        if current_session is not None:
            spread_queryset = spread_queryset.exclude(session=current_session)
        spread_rows = (
            spread_queryset
            .values(
                'field_id',
                'field__name',
                'field__area_tan',
                'fertilizer_id',
                'fertilizer__name',
            )
            .annotate(spread_bags=Sum('actual_bags'))
        )
        for row in spread_rows:
            key = (row['field_id'], row['fertilizer_id'])
            candidates.setdefault(
                key,
                {
                    'field': row['field_id'],
                    'field_name': row['field__name'],
                    'field_area_tan': str(row['field__area_tan']),
                    'fertilizer': row['fertilizer_id'],
                    'fertilizer_name': row['fertilizer__name'],
                    'planned_bags': Decimal('0'),
                    'delivered_bags': Decimal('0'),
                    'spread_bags': Decimal('0'),
                    'current_session_bags': Decimal('0'),
                },
            )['spread_bags'] = row['spread_bags'] or Decimal('0')

        for key, current_data in current_map.items():
            candidates.setdefault(
                key,
                {
                    'field': key[0],
                    'field_name': current_data['field_name'],
                    'field_area_tan': current_data['field_area_tan'],
                    'fertilizer': key[1],
                    'fertilizer_name': current_data['fertilizer_name'],
                    'planned_bags': Decimal('0'),
                    'delivered_bags': Decimal('0'),
                    'spread_bags': Decimal('0'),
                    'current_session_bags': Decimal('0'),
                },
            )['current_session_bags'] = current_data['actual_bags'] or Decimal('0')

        rows = []
        for candidate in candidates.values():
            delivered = candidate['delivered_bags']
            planned = candidate['planned_bags']
            current_bags = candidate['current_session_bags']
            if delivery_plan_id:
                include_row = delivered > 0 or current_bags > 0
            elif plan_id:
                include_row = planned > 0 or current_bags > 0
            else:
                include_row = delivered > 0 or current_bags > 0
            if not include_row:
                continue
            remaining = delivered - candidate['spread_bags']
            rows.append(
                {
                    'field': candidate['field'],
                    'field_name': candidate['field_name'],
                    'field_area_tan': candidate['field_area_tan'],
                    'fertilizer': candidate['fertilizer'],
                    'fertilizer_name': candidate['fertilizer_name'],
                    'planned_bags': str(planned),
                    'delivered_bags': str(delivered),
                    'spread_bags': str(candidate['spread_bags'] + current_bags),
                    'spread_bags_other': str(candidate['spread_bags']),
                    'current_session_bags': str(current_bags),
                    'remaining_bags': str(remaining),
                }
            )

        rows.sort(key=lambda row: (row['field_name'], row['fertilizer_name']))
        return Response(rows)
