import csv
from io import TextIOWrapper
from io import BytesIO
from decimal import Decimal
from calendar import monthrange
from django.utils import timezone
from django.http import HttpResponse

from rest_framework import generics, permissions, status
from rest_framework import decorators, viewsets
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import BankAccount, WithdrawalDeductionConfig, WithdrawalRequest
from apps.business.wallet.models import Wallet
from .serializers import (
    AdminWithdrawalRequestSerializer,
    BankAccountSerializer,
    WithdrawalDeductionConfigSerializer,
    WithdrawalRequestSerializer,
)
from .services.withdrawal_service import create_withdrawal_request, process_withdrawal
from .services.otp_service import send_bank_account_otp, verify_bank_account_otp

from apps.platform.accounts.throttles import WithdrawalRateThrottle


def _next_month_last_day(now):
    year = now.year + (1 if now.month == 12 else 0)
    month = 1 if now.month == 12 else now.month + 1
    return now.replace(year=year, month=month, day=monthrange(year, month)[1])


# ---------------------------------------------------------------------------
# Admin: Deduction Config
# ---------------------------------------------------------------------------

class WithdrawalDeductionConfigAdminViewSet(viewsets.ModelViewSet):
    queryset = WithdrawalDeductionConfig.objects.all().order_by('-is_active', '-updated_at')
    serializer_class = WithdrawalDeductionConfigSerializer
    permission_classes = [permissions.IsAdminUser]

    @decorators.action(detail=True, methods=['post'])
    def activate(self, request, pk=None):
        config = self.get_object()
        config.is_active = True
        config.save(update_fields=['is_active', 'updated_at'])
        return Response({'success': True, 'id': str(config.id), 'is_active': True}, status=status.HTTP_200_OK)


# ---------------------------------------------------------------------------
# Admin: Withdrawal Requests
# ---------------------------------------------------------------------------

class AdminWithdrawalRequestListView(generics.ListAPIView):
    serializer_class = AdminWithdrawalRequestSerializer
    permission_classes = [permissions.IsAdminUser]

    def get_queryset(self):
        qs = WithdrawalRequest.objects.select_related('user', 'bank_account').order_by('-created_at')
        status_filter = self.request.query_params.get('status')
        if status_filter:
            qs = qs.filter(status=status_filter.upper())
        return qs


class AdminWithdrawalRequestActionView(APIView):
    permission_classes = [permissions.IsAdminUser]

    def post(self, request, pk):
        action = request.data.get('action', '').upper()
        remarks = request.data.get('remarks', '')
        if action not in ('APPROVE', 'REJECT', 'PAID'):
            return Response({'success': False, 'error': 'Invalid action. Use APPROVE, REJECT or PAID.'}, status=status.HTTP_400_BAD_REQUEST)
        if action == 'REJECT' and not remarks:
            return Response({'success': False, 'error': 'Remarks are required when rejecting a withdrawal.'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            withdrawal = process_withdrawal(pk, action, remarks=remarks or None)
            serializer = AdminWithdrawalRequestSerializer(withdrawal)
            return Response({'success': True, 'data': serializer.data})
        except WithdrawalRequest.DoesNotExist:
            return Response({'success': False, 'error': 'Withdrawal request not found.'}, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({'success': False, 'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)


class AdminWithdrawalBulkActionView(APIView):
    permission_classes = [permissions.IsAdminUser]

    def post(self, request):
        action = request.data.get('action', '').upper()
        ids = request.data.get('ids', [])
        remarks = request.data.get('remarks', '')
        if action not in ('APPROVE', 'REJECT'):
            return Response({'success': False, 'error': 'Bulk action must be APPROVE or REJECT.'}, status=status.HTTP_400_BAD_REQUEST)
        if action == 'REJECT' and not remarks:
            return Response({'success': False, 'error': 'Remarks are required for bulk rejection.'}, status=status.HTTP_400_BAD_REQUEST)
        if not ids or not isinstance(ids, list):
            return Response({'success': False, 'error': 'ids must be a non-empty list.'}, status=status.HTTP_400_BAD_REQUEST)

        results = {'success': [], 'failed': []}
        for pk in ids:
            try:
                process_withdrawal(pk, action, remarks=remarks or None)
                results['success'].append(pk)
            except Exception as e:
                results['failed'].append({'id': pk, 'error': str(e)})

        return Response({'success': True, 'results': results})


class AdminWithdrawalApprovedExportView(APIView):
    permission_classes = [permissions.IsAdminUser]

    def get(self, request):
        qs = (
            WithdrawalRequest.objects
            .select_related('user', 'bank_account', 'deduction_config')
            .filter(status=WithdrawalRequest.Status.APPROVED)
            .order_by('created_at')
        )

        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="approved_withdrawals.csv"'
        writer = csv.writer(response)
        writer.writerow([
            'reference_id',
            'user_name',
            'user_mobile',
            'source',
            'gross_amount',
            'net_amount',
            'total_deduction',
            'bank_name',
            'account_number_masked',
            'ifsc_code',
            'status',
            'created_at',
        ])

        for row in qs:
            user_name = f"{getattr(row.user, 'first_name', '')} {getattr(row.user, 'last_name', '')}".strip() or getattr(row.user, 'email', '') or str(row.user_id)
            user_mobile = getattr(row.user, 'mobile', None) or getattr(row.user, 'phone_number', None) or ''
            gross_amount = row.upstream_gross_amount or row.amount_requested
            writer.writerow([
                row.reference_id,
                user_name,
                user_mobile,
                row.source or 'direct_incentive',
                str(gross_amount),
                str(row.net_amount),
                str(row.total_deduction_amount),
                row.bank_account.bank_name if row.bank_account else '',
                row.bank_account.masked_account_number if row.bank_account else '',
                row.bank_account.ifsc_code if row.bank_account else '',
                row.status,
                row.created_at.isoformat(),
            ])
        return response


class AdminWithdrawalBulkPaidUploadView(APIView):
    permission_classes = [permissions.IsAdminUser]
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        upload = request.FILES.get('file')
        if not upload:
            return Response({'success': False, 'error': 'Please upload a CSV or Excel (.xlsx) file.'}, status=status.HTTP_400_BAD_REQUEST)

        filename = str(getattr(upload, 'name', '')).lower()
        rows = []
        try:
            if filename.endswith('.csv'):
                text_stream = TextIOWrapper(upload.file, encoding='utf-8-sig')
                rows = list(csv.DictReader(text_stream))
            elif filename.endswith('.xlsx'):
                try:
                    from openpyxl import load_workbook
                except Exception:
                    return Response(
                        {'success': False, 'error': 'Excel upload requires openpyxl on server. Please upload CSV for now.'},
                        status=status.HTTP_400_BAD_REQUEST,
                    )

                wb = load_workbook(filename=BytesIO(upload.read()), read_only=True, data_only=True)
                ws = wb.active
                iterator = ws.iter_rows(values_only=True)
                headers = next(iterator, None)
                if not headers:
                    return Response({'success': False, 'error': 'Excel file is empty.'}, status=status.HTTP_400_BAD_REQUEST)
                normalized_headers = [str(h).strip() if h is not None else '' for h in headers]
                for excel_row in iterator:
                    row_dict = {}
                    for idx, cell in enumerate(excel_row):
                        if idx < len(normalized_headers):
                            row_dict[normalized_headers[idx]] = '' if cell is None else str(cell)
                    rows.append(row_dict)
            else:
                return Response({'success': False, 'error': 'Unsupported file type. Upload CSV or .xlsx file.'}, status=status.HTTP_400_BAD_REQUEST)
        except Exception as e:
            return Response({'success': False, 'error': f'Invalid upload file: {e}'}, status=status.HTTP_400_BAD_REQUEST)

        results = {'success': [], 'failed': []}
        found_any = False

        for idx, row in enumerate(rows, start=2):
            reference_id = str(
                row.get('reference_id')
                or row.get('reference')
                or row.get('Reference')
                or row.get('REFERENCE_ID')
                or ''
            ).strip()
            if not reference_id:
                continue
            found_any = True
            try:
                withdrawal = WithdrawalRequest.objects.get(reference_id=reference_id)
                process_withdrawal(withdrawal.id, 'PAID')
                results['success'].append({'reference_id': reference_id, 'id': withdrawal.id})
            except WithdrawalRequest.DoesNotExist:
                results['failed'].append({'reference_id': reference_id, 'line': idx, 'error': 'Reference not found'})
            except Exception as e:
                results['failed'].append({'reference_id': reference_id, 'line': idx, 'error': str(e)})

        if not found_any:
            return Response({'success': False, 'error': 'No reference_id rows found in uploaded CSV.'}, status=status.HTTP_400_BAD_REQUEST)

        return Response({'success': True, 'results': results})


# ---------------------------------------------------------------------------
# User: Bank Accounts
# ---------------------------------------------------------------------------

class BankAccountListCreateView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        accounts = BankAccount.objects.filter(user=request.user, is_active=True)
        serializer = BankAccountSerializer(accounts, many=True)
        return Response({'success': True, 'data': serializer.data})

    def post(self, request):
        serializer = BankAccountSerializer(data=request.data)
        if not serializer.is_valid():
            return Response({'success': False, 'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
        # Limit: max 3 bank accounts per user
        if BankAccount.objects.filter(user=request.user, is_active=True).count() >= 3:
            return Response({'success': False, 'error': 'Maximum 3 bank accounts allowed.'}, status=status.HTTP_400_BAD_REQUEST)
        bank_account = serializer.save(user=request.user, is_verified=False)
        # Auto-set as primary if it's the first one
        if not BankAccount.objects.filter(user=request.user, is_active=True, is_primary=True).exclude(pk=bank_account.pk).exists():
            bank_account.is_primary = True
            bank_account.save(update_fields=['is_primary'])
        return Response({'success': True, 'data': BankAccountSerializer(bank_account).data}, status=status.HTTP_201_CREATED)


class BankAccountDetailView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def _get_account(self, request, pk):
        try:
            return BankAccount.objects.get(pk=pk, user=request.user, is_active=True)
        except BankAccount.DoesNotExist:
            return None

    def delete(self, request, pk):
        account = self._get_account(request, pk)
        if not account:
            return Response({'success': False, 'error': 'Bank account not found.'}, status=status.HTTP_404_NOT_FOUND)
        account.is_active = False
        account.save(update_fields=['is_active'])
        return Response({'success': True})

    def patch(self, request, pk):
        account = self._get_account(request, pk)
        if not account:
            return Response({'success': False, 'error': 'Bank account not found.'}, status=status.HTTP_404_NOT_FOUND)
        # Only allow setting primary
        if request.data.get('is_primary'):
            account.is_primary = True
            account.save(update_fields=['is_primary'])
        return Response({'success': True, 'data': BankAccountSerializer(account).data})


class BankAccountSendOTPView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, pk):
        try:
            account = BankAccount.objects.get(pk=pk, user=request.user, is_active=True)
        except BankAccount.DoesNotExist:
            return Response({'success': False, 'error': 'Bank account not found.'}, status=status.HTTP_404_NOT_FOUND)
        if account.is_verified:
            return Response({'success': False, 'error': 'Bank account is already verified.'}, status=status.HTTP_400_BAD_REQUEST)
        result = send_bank_account_otp(account)
        return Response({'success': True, 'data': result})


class BankAccountVerifyOTPView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request, pk):
        try:
            account = BankAccount.objects.get(pk=pk, user=request.user, is_active=True)
        except BankAccount.DoesNotExist:
            return Response({'success': False, 'error': 'Bank account not found.'}, status=status.HTTP_404_NOT_FOUND)
        if account.is_verified:
            return Response({'success': False, 'error': 'Bank account is already verified.'}, status=status.HTTP_400_BAD_REQUEST)
        otp_code = request.data.get('otp', '')
        if not otp_code:
            return Response({'success': False, 'error': 'OTP is required.'}, status=status.HTTP_400_BAD_REQUEST)
        verified = verify_bank_account_otp(account, otp_code)
        if not verified:
            return Response({'success': False, 'error': 'Invalid or expired OTP.'}, status=status.HTTP_400_BAD_REQUEST)
        return Response({'success': True, 'data': BankAccountSerializer(account).data})


# ---------------------------------------------------------------------------
# User: Withdrawal Requests
# ---------------------------------------------------------------------------

class WithdrawalRequestCreateView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    throttle_classes = [WithdrawalRateThrottle]

    def post(self, request):
        user = request.user
        amount = request.data.get('amount')
        bank_account_id = request.data.get('bank_account_id')
        source = str(request.data.get('source') or '').strip().lower()

        if not user or not user.is_authenticated:
            return Response({'success': False, 'error': 'Authentication required.'}, status=status.HTTP_401_UNAUTHORIZED)
        if 'user' in request.data and str(request.data['user']) != str(user.id):
            return Response({'success': False, 'error': 'You can only request withdrawal for your own account.'}, status=status.HTTP_403_FORBIDDEN)

        try:
            amount_decimal = Decimal(str(amount))
            if amount_decimal <= 0:
                return Response({'success': False, 'error': 'Withdrawal amount must be positive.'}, status=status.HTTP_400_BAD_REQUEST)
        except Exception:
            return Response({'success': False, 'error': 'Invalid withdrawal amount.'}, status=status.HTTP_400_BAD_REQUEST)

        bank_account = None
        if bank_account_id:
            try:
                bank_account = BankAccount.objects.get(pk=bank_account_id, user=user, is_active=True, is_verified=True)
            except BankAccount.DoesNotExist:
                return Response({'success': False, 'error': 'Selected bank account not found or not verified.'}, status=status.HTTP_400_BAD_REQUEST)

        try:
            is_sponsor_ledger = source == 'sponsor_ledger_net'
            is_power_stream = source == 'power_stream'
            apply_deductions = not is_sponsor_ledger
            enforce_last_day = is_power_stream
            enforce_monthly_limit = is_power_stream
            upstream_total_deduction = None
            upstream_gross_amount = None
            if source == 'sponsor_ledger_net':
                try:
                    upstream_total_deduction = Decimal(str(request.data.get('upstream_total_deduction') or '0'))
                except Exception:
                    upstream_total_deduction = None
                try:
                    upstream_gross_amount = Decimal(str(request.data.get('upstream_gross_amount') or '0'))
                except Exception:
                    upstream_gross_amount = None
            withdrawal = create_withdrawal_request(
                user,
                amount_decimal,
                bank_account=bank_account,
                enforce_last_day=enforce_last_day,
                apply_deductions=apply_deductions,
                enforce_monthly_limit=enforce_monthly_limit,
                upstream_total_deduction=upstream_total_deduction,
                upstream_gross_amount=upstream_gross_amount,
                source=source,
            )
            serializer = WithdrawalRequestSerializer(withdrawal)
            return Response({'success': True, 'data': serializer.data}, status=status.HTTP_201_CREATED)
        except Exception as e:
            return Response({'success': False, 'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)


class WithdrawalRequestHistoryView(generics.ListAPIView):
    serializer_class = WithdrawalRequestSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return WithdrawalRequest.objects.filter(user=self.request.user).select_related('bank_account').order_by('-created_at')


class WithdrawalDeductionConfigPublicView(APIView):
    """Public read-only endpoint: exposes TDS/fee info so users can preview deductions."""
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        config = WithdrawalDeductionConfig.objects.filter(is_active=True).order_by('-updated_at').first()
        if not config:
            return Response({
                'tds_percent': '5.000',
                'repurchase_percent': '5.000',
                'welfare_fund_percent': '5.000',
                'technology_charges_percent': '5.000',
                'operations_charges_percent': '5.000',
                'admin_fee_percent': '0.000',
                'processing_fee_fixed': '0.00',
            })
        return Response({
            'tds_percent': str(config.tds_percent),
            'repurchase_percent': str(config.repurchase_percent),
            'welfare_fund_percent': str(config.welfare_fund_percent),
            'technology_charges_percent': str(config.technology_charges_percent),
            'operations_charges_percent': str(config.operations_charges_percent),
            'admin_fee_percent': str(config.admin_fee_percent),
            'processing_fee_fixed': str(config.processing_fee_fixed),
        })


class WithdrawalEligibilityView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def get(self, request):
        now = timezone.now()
        last_day = monthrange(now.year, now.month)[1]
        is_last_day = now.day == last_day

        wallet, _ = Wallet.objects.get_or_create(user=request.user)
        available = max(Decimal('0.00'), Decimal(wallet.main_balance) - Decimal(wallet.locked_amount))

        if is_last_day:
            next_date = _next_month_last_day(now)
        else:
            next_date = now.replace(day=last_day)

        return Response(
            {
                'success': True,
                'data': {
                    'is_withdrawal_day': is_last_day,
                    'eligible_amount': str(available),
                    'next_withdrawal_date': next_date.date().isoformat(),
                },
            }
        )

