from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_jwt.settings import api_settings
from django.contrib.auth import authenticate
from django.db import transaction
from django.db.models import Q
from customers.models import Customer
from mmbanking.models import MobileBankingSubscription
from users.models import User, UserSession
from customers.serializers import CustomerSerializer
from users.serializers import UserSerializer
from exservices.exservices_helper import send_otp_pin
from exservices.models import OrganisationSmsSubscription
from users.rate_limiting import rate_limit_otp
from users.views import RestAPIJWT
from mobileapp.models import MobileCustomerOtpState
from license.helpers import get_organisation_access_state
import random
from django.utils import timezone
import hashlib
import hmac
import secrets

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER


def get_customer_by_identifier(identifier):
    if not identifier:
        return None
    return Customer.objects.filter(
        Q(member_number=identifier) |
        Q(old_member_number=identifier) |
        Q(telephone=identifier),
        is_deleted=False
    ).first()


def get_customer_from_token_request(request):
    customer_id = None
    token = getattr(request, 'auth', None)

    if token:
        try:
            payload = jwt_decode_handler(token)
            customer_id = payload.get('customer_id')
        except Exception:
            customer_id = None

    if not customer_id:
        customer_id = getattr(request.user, 'customer_id', None)

    if not customer_id:
        return None
    return Customer.objects.filter(id=customer_id, is_deleted=False).first()


class MobileAuthLoginView(APIView):
    """
    Mobile app authentication endpoint
    Supports login with member number or mobile number
    """
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request, format=None):
        identifier = request.data.get('identifier')  # member_number or mobile_number
        pin = request.data.get('pin')
        
        if not identifier:
            return Response({
                'status': False,
                'message': 'Member number or mobile number is required'
            }, status=status.HTTP_400_BAD_REQUEST)

        # Try to find customer by member number or mobile number
        customer = get_customer_by_identifier(identifier)

        if not customer:
            return Response({
                'status': False,
                'message': 'Customer not found'
            }, status=status.HTTP_404_NOT_FOUND)

        organisation = getattr(customer.customer_branch, "branch_organisation", None)
        if not organisation or not organisation.is_mobile_app_active:
            return Response({
                'status': False,
                'message': 'Your organisation is not activated for mobile app'
            }, status=status.HTTP_403_FORBIDDEN)

        access_state = get_organisation_access_state(organisation=organisation, force_sync=True)
        if not access_state['allowed']:
            return Response({
                'status': False,
                'message': access_state['message']
            }, status=status.HTTP_403_FORBIDDEN)

        # Check if customer has active savings account
        from savings.models import SavingAccount
        has_active_account = SavingAccount.objects.filter(
            account_customer=customer,
            status='active'
        ).exists()

        if not has_active_account:
            return Response({
                'status': False,
                'message': 'No active account found for this customer'
            }, status=status.HTTP_403_FORBIDDEN)

        # Get a system user for JWT token
        system_user = User.objects.filter(
            user_organisation_branch=customer.customer_branch,
            is_active=True
        ).first()

        if not system_user:
            return Response({
                'status': False,
                'message': 'System error. Please contact support.'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # Generate JWT token with customer ID for proper authentication
        payload = jwt_payload_handler(system_user)
        payload['customer_id'] = customer.id  # Add customer ID to token
        token = jwt_encode_handler(payload)

        UserSession.objects.update_or_create(
            user=system_user,
            session_token=token,
            defaults={
                'allow_access': True,
                'data': {
                    'organisation_id': customer.customer_branch.branch_organisation.id,
                    'organisation_branch_id': customer.customer_branch.id
                }
            }
        )

        return Response({
            'status': True,
            'message': 'Login successful',
            'data': {
                'token': token,
                'customer': CustomerSerializer(customer).data,
                'user': {
                    'id': system_user.id,
                    'username': system_user.username,
                    'organisation': customer.customer_branch.branch_organisation.name,
                    'branch': customer.customer_branch.name
                }
            }
        }, status=status.HTTP_200_OK)


class MobileOfficerAuthLoginView(RestAPIJWT):
    """
    Officer mobile-app login endpoint.
    Returns a clear activation error when the officer's organisation
    is not enabled for mobile app access.
    """

    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')

        user = authenticate(username=username, password=password)
        if user:
            organisation = getattr(getattr(user, "user_organisation_branch", None), "branch_organisation", None)
            if not organisation or not organisation.is_mobile_app_active:
                return Response({
                    "status": False,
                    "message": "Your organisation is not activated for mobile app"
                }, status=status.HTTP_403_FORBIDDEN)

        return super().post(request, *args, **kwargs)


class VerifyCustomerOTPView(APIView):
    permission_classes = [AllowAny]

    def _session_hash(self, token):
        return hashlib.sha256((token or "").encode("utf-8")).hexdigest()

    def _get_or_create_state(self, customer, token):
        session_hash = self._session_hash(token)
        state, _ = MobileCustomerOtpState.objects.get_or_create(
            customer=customer,
            session_token_hash=session_hash,
            defaults={"last_updated": timezone.now()},
        )
        return state

    def _build_hash(self, otp, salt):
        digest = hashlib.sha256(f"{salt}:{otp}".encode("utf-8")).hexdigest()[:32]
        return f"v1${salt}${digest}"

    @rate_limit_otp(max_requests=10, window_minutes=15)
    def post(self, request, format=None):
        action = request.data.get("action")
        pin = request.data.get("pin")
        user = request.user

        if action not in ["send_otp", "verify_otp"]:
            return Response({"status": False, "message": "Invalid action."}, status=status.HTTP_400_BAD_REQUEST)

        if not user or not user.is_authenticated:
            return Response({"status": False, "message": "Authentication required."}, status=status.HTTP_401_UNAUTHORIZED)

        auth_header = request.META.get("HTTP_AUTHORIZATION", "")
        token = None
        if auth_header and " " in auth_header:
            token = auth_header.split(" ", 1)[1].strip()
        elif auth_header:
            token = auth_header.strip()

        if not token:
            return Response({"status": False, "message": "Authorization token required."}, status=status.HTTP_401_UNAUTHORIZED)

        user_session = UserSession.objects.filter(user=user, session_token=token).first()
        if not user_session:
            return Response({"status": False, "message": "Invalid or expired session."}, status=status.HTTP_401_UNAUTHORIZED)

        customer = get_customer_from_token_request(request)
        if not customer:
            return Response({"status": False, "message": "Customer not found for authenticated user."}, status=status.HTTP_404_NOT_FOUND)

        subscription = MobileBankingSubscription.objects.filter(customer=customer, active=True).first()
        if not subscription:
            return Response(
                {"status": False, "message": "Mobile banking not activated for this customer."},
                status=status.HTTP_404_NOT_FOUND,
            )

        organisation = customer.customer_branch.branch_organisation
        org_sms_sub = OrganisationSmsSubscription.objects.filter(
            organisation=organisation,
            is_subscribed=True,
            sms_type__sms_type_key="2fa_sms",
            sms_type__status="active",
        ).first()
        if not org_sms_sub:
            return Response(
                {"status": False, "message": "Organisation is not subscribed to 2FA SMS."},
                status=status.HTTP_403_FORBIDDEN,
            )

        now = timezone.now()
        with transaction.atomic():
            state = self._get_or_create_state(customer, token)

            if state.locked_until and now < state.locked_until:
                user_session.allow_access = False
                user_session.save(update_fields=["allow_access"])
                return Response({"status": False, "message": "Account temporarily locked. Try again later."})

            if action == "send_otp":
                if state.last_request and now <= state.last_request + timezone.timedelta(minutes=1):
                    return Response({"status": False, "message": "Please wait before requesting another OTP."})

                generated_pin = random.randint(1000, 9999)
                salt = secrets.token_hex(4)
                state.otp_hash = self._build_hash(str(generated_pin), salt)
                state.otp_expires_at = now + timezone.timedelta(minutes=5)
                state.last_request = now
                state.last_updated = now
                state.save(update_fields=["otp_hash", "otp_expires_at", "last_request", "last_updated"])

                subscriber_data = {
                    "sms_type": None,
                    "charge": 0,
                    "charged_to": "sacco",
                    "telephone": customer.telephone,
                    "recieved_by": customer.id,
                    "is_subscribed": False,
                    "debit_chart": None,
                    "free_sms": 0,
                    "sms_unique_key": "",
                    "recieved_by_type": "customer",
                    "reciever_name": customer.name,
                    "sent_by": subscription.added_by,
                    "branch": customer.customer_branch,
                    "message": f"Your login OTP is: {generated_pin}. Valid for 5 minutes. Do not share.\nQuest Banker",
                }
                sms_data = send_otp_pin(subscriber_data)
                if not sms_data.get("status"):
                    return Response(
                        {
                            "status": False,
                            "message": sms_data.get("comment", "Failed to send OTP. Please try again."),
                        }
                    )
                return Response({"status": True, "message": "", "telephone": customer.telephone})

            otp_input = "".join(ch for ch in str(pin or "") if ch.isdigit())
            if otp_input == "":
                return Response({"status": False, "message": "OTP pin is required."}, status=status.HTTP_400_BAD_REQUEST)

            is_verified = False
            if state.otp_hash and str(state.otp_hash).startswith("v1$") and state.otp_expires_at and now <= state.otp_expires_at:
                try:
                    _, salt, expected = str(state.otp_hash).split("$", 2)
                    actual = self._build_hash(otp_input, salt).split("$", 2)[2]
                    is_verified = hmac.compare_digest(actual, expected)
                except Exception:
                    is_verified = False

            if is_verified:
                state.attempts = 0
                state.locked_until = None
                state.otp_hash = None
                state.otp_expires_at = None
                state.last_updated = now
                state.save(update_fields=["attempts", "locked_until", "otp_hash", "otp_expires_at", "last_updated"])
                user_session.allow_access = True
                user_session.save(update_fields=["allow_access"])
                return Response({"status": True, "message": "OTP verified successfully"})

            state.attempts = int(state.attempts or 0) + 1
            user_session.allow_access = False
            user_session.save(update_fields=["allow_access"])
            if state.attempts >= 5:
                state.locked_until = now + timezone.timedelta(minutes=15)
                state.last_updated = now
                state.save(update_fields=["attempts", "locked_until", "last_updated"])
                return Response({"status": False, "message": "Too many failed attempts. Account locked for 15 minutes."})

            state.last_updated = now
            state.save(update_fields=["attempts", "last_updated"])
            attempts_left = max(0, 5 - state.attempts)
            return Response({"status": False, "message": f"Invalid OTP. {attempts_left} attempts remaining."})


class MobilePortfolioSavingsView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        customer = get_customer_from_token_request(request)
        if not customer:
            return Response({
                'status': False,
                'message': 'Customer not found for authenticated user'
            }, status=status.HTTP_404_NOT_FOUND)

        from savings.models import SavingAccount
        from savings.savings_bal_helper import get_account_balance

        savings_accounts = SavingAccount.objects.filter(
            account_customer=customer,
            status='active'
        )
        savings_data = []
        total_savings_balance = 0.0

        for account in savings_accounts:
            balance_info = get_account_balance(account)
            balance = balance_info.get('balance', 0)
            if isinstance(balance, str):
                balance = float(balance.replace(',', ''))
            else:
                balance = float(balance)

            savings_data.append({
                'account_id': account.id,
                'product_name': account.account_product.product_name,
                'account_number': account.account_no,
                'balance': balance,
                'available_balance': float(str(balance_info.get('available_balance', 0)).replace(',', '')),
                'blocked_amount': float(str(balance_info.get('blocked_amount', 0)).replace(',', ''))
            })
            total_savings_balance += balance

        return Response({
            'status': True,
            'message': 'Savings portfolio fetched',
            'data': {
                'customer_id': customer.id,
                'savings': {
                    'accounts': savings_data,
                    'total_balance': total_savings_balance
                }
            }
        }, status=status.HTTP_200_OK)


class MobilePortfolioLoansView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        customer = get_customer_from_token_request(request)
        if not customer:
            return Response({
                'status': False,
                'message': 'Customer not found for authenticated user'
            }, status=status.HTTP_404_NOT_FOUND)

        from datetime import date
        from django.db import connection
        from loans.models import LoanApplication

        loans = LoanApplication.objects.filter(
            customer=customer,
            status__in=['disbursed', 'approved']
        )
        loans_data = []
        total_loan_balance = 0.0
        as_at = date.today().strftime('%Y-%m-%d')

        for loan in loans:
            with connection.cursor() as cursor:
                cursor.execute(
                    "SELECT princ_paid, int_paid, total_principal_expected, total_interest_expected "
                    "FROM loan_repayment_func(%s) WHERE id = %s;",
                    [as_at, loan.id]
                )
                row = cursor.fetchone()

                if row:
                    princ_paid, int_paid, total_principal_expected, total_interest_expected = row
                    loan_balance = (total_principal_expected or 0) - (princ_paid or 0)
                else:
                    princ_paid = 0
                    int_paid = 0
                    loan_balance = loan.loan_amount

            loans_data.append({
                'loan_id': loan.id,
                'product_name': loan.loan_application_product.product_name,
                'loan_amount': float(loan.loan_amount),
                'principal_paid': float(princ_paid or 0),
                'interest_paid': float(int_paid or 0),
                'balance': float(loan_balance),
                'status': loan.status,
                'disbursement_date': loan.disbursement_date if hasattr(loan, 'disbursement_date') else None
            })
            total_loan_balance += float(loan_balance)

        return Response({
            'status': True,
            'message': 'Loans portfolio fetched',
            'data': {
                'customer_id': customer.id,
                'loans': {
                    'accounts': loans_data,
                    'total_balance': total_loan_balance
                }
            }
        }, status=status.HTTP_200_OK)


class MobilePortfolioSharesView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        customer = get_customer_from_token_request(request)
        if not customer:
            return Response({
                'status': False,
                'message': 'Customer not found for authenticated user'
            }, status=status.HTTP_404_NOT_FOUND)

        from shares.models import SharesTransaction
        from shares.serializers import SharesLedgerSerializer

        share_transactions = SharesTransaction.objects.filter(
            shareholder__customer=customer,
            is_deleted=False,
            deleted=False
        ).order_by('id')

        return Response({
            'shares': SharesLedgerSerializer(share_transactions, many=True).data
        }, status=status.HTTP_200_OK)


class MobilePortfolioTotalsView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        customer = get_customer_from_token_request(request)
        if not customer:
            return Response({
                'status': False,
                'message': 'Customer not found for authenticated user'
            }, status=status.HTTP_404_NOT_FOUND)

        from datetime import date
        from django.db import connection
        from savings.models import SavingAccount
        from savings.savings_bal_helper import get_account_balance
        from loans.models import LoanApplication
        from shares.shares_balance_helper import get_client_shares_balance

        total_savings_balance = 0.0
        savings_accounts = SavingAccount.objects.filter(
            account_customer=customer,
            status='active'
        )
        for account in savings_accounts:
            balance = get_account_balance(account).get('balance', 0)
            if isinstance(balance, str):
                balance = float(balance.replace(',', ''))
            else:
                balance = float(balance)
            total_savings_balance += balance

        total_loan_balance = 0.0
        loans = LoanApplication.objects.filter(
            customer=customer,
            status__in=['disbursed', 'approved']
        )
        as_at = date.today().strftime('%Y-%m-%d')
        for loan in loans:
            with connection.cursor() as cursor:
                cursor.execute(
                    "SELECT princ_paid, total_principal_expected "
                    "FROM loan_repayment_func(%s) WHERE id = %s;",
                    [as_at, loan.id]
                )
                row = cursor.fetchone()

                if row:
                    princ_paid, total_principal_expected = row
                    loan_balance = (total_principal_expected or 0) - (princ_paid or 0)
                else:
                    loan_balance = loan.loan_amount
            total_loan_balance += float(loan_balance)

        shares_info = get_client_shares_balance(customer) or {}
        total_shares_balance = float(
            shares_info.get('shares_value', shares_info.get('total_share_value', 0)) or 0
        )

        return Response({
            'status': True,
            'message': 'Portfolio totals fetched',
            'data': {
                'customer_id': customer.id,
                'totals': {
                    'savings': total_savings_balance,
                    'loans': total_loan_balance,
                    'shares': total_shares_balance
                }
            }
        }, status=status.HTTP_200_OK)


class MobileAuthRegisterView(APIView):
    """
    Mobile app registration endpoint
    Creates customer and mobile banking subscription
    """
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request, format=None):
        name = request.data.get('name')
        telephone = request.data.get('telephone')
        gender = request.data.get('gender', 'O')
        branch_id = request.data.get('branch_id')
        customer_type_id = request.data.get('customer_type_id')

        if not all([name, telephone, branch_id, customer_type_id]):
            return Response({
                'status': False,
                'message': 'Name, telephone, branch_id, and customer_type_id are required'
            }, status=status.HTTP_400_BAD_REQUEST)

        # Check if customer already exists
        existing_customer = Customer.objects.filter(
            telephone=telephone,
            is_deleted=False
        ).first()

        if existing_customer:
            return Response({
                'status': False,
                'message': 'Customer with this phone number already exists'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            from customers.helper import generate_member_number
            from organisations.models import OrganisationBranch, CustomerType
            
            # Get branch and customer type
            branch = OrganisationBranch.objects.get(id=branch_id)
            customer_type = CustomerType.objects.get(id=customer_type_id)
            
            # Get system user for creation
            system_user = User.objects.filter(
                user_organisation_branch=branch,
                is_active=True
            ).first()

            if not system_user:
                return Response({
                    'status': False,
                    'message': 'System error. Please contact support.'
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

            # Generate member number
            member_number = generate_member_number(branch_id)

            # Create customer
            customer = Customer.objects.create(
                name=name,
                member_number=member_number,
                telephone=telephone,
                gender=gender,
                customer_branch=branch,
                branch_customer_type=customer_type,
                customer_added_by=system_user,
                customer_officer=system_user.user_staff,
                date_added=timezone.now()
            )

            # Generate PIN and create mobile banking subscription
            pin = random.randint(1000, 9999)
            subscription = MobileBankingSubscription.objects.create(
                customer=customer,
                pin=str(pin),
                active=True,
                telephone_no=telephone,
                added_by=system_user
            )

            # Send SMS with PIN (optional - implement if SMS service is available)
            try:
                from exservices.exservices_helper import send_sms, format_phone_number
                formatted_phone = format_phone_number(telephone)
                message = f"Dear {name}, Welcome to {branch.branch_organisation.name}! Your member number is {member_number} and PIN is {pin}. Download our mobile app to get started."
                # send_sms(formatted_phone, message)  # Uncomment if SMS service is configured
            except Exception as e:
                print(f"SMS sending failed: {str(e)}")

            return Response({
                'status': True,
                'message': 'Registration successful',
                'data': {
                    'customer': CustomerSerializer(customer).data,
                    'pin': pin,  # In production, don't return PIN in response
                    'member_number': member_number
                }
            }, status=status.HTTP_201_CREATED)

        except Exception as e:
            return Response({
                'status': False,
                'message': f'Registration failed: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class MobileAuthResetPinView(APIView):
    """
    Reset mobile banking PIN
    """
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request, format=None):
        identifier = request.data.get('identifier')  # member_number or mobile_number
        
        if not identifier:
            return Response({
                'status': False,
                'message': 'Member number or mobile number is required'
            }, status=status.HTTP_400_BAD_REQUEST)

        # Find customer
        customer = Customer.objects.filter(
            Q(member_number=identifier) | 
            Q(old_member_number=identifier) | 
            Q(telephone=identifier),
            is_deleted=False
        ).first()

        if not customer:
            return Response({
                'status': False,
                'message': 'Customer not found'
            }, status=status.HTTP_404_NOT_FOUND)

        # Find subscription
        subscription = MobileBankingSubscription.objects.filter(
            customer=customer,
            active=True
        ).first()

        if not subscription:
            return Response({
                'status': False,
                'message': 'Mobile banking not activated for this customer'
            }, status=status.HTTP_404_NOT_FOUND)

        # Generate new PIN
        new_pin = random.randint(1000, 9999)
        subscription.pin = str(new_pin)
        subscription.save()

        return Response({
            'status': True,
            'message': 'PIN reset successful. New PIN sent via SMS.',
            'data': {
                'pin': new_pin  # In production, don't return PIN in response
            }
        }, status=status.HTTP_200_OK)

class MobileAuthValidateTokenView(APIView):
    """
    Validate JWT token and return user info
    """
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        # Get customer from token
        customer = get_customer_from_token_request(request)
        
        return Response({
            'status': True,
            'message': 'Token is valid',
            'data': {
                'user': UserSerializer(request.user).data,
                'customer': CustomerSerializer(customer).data if customer else None
            }
        }, status=status.HTTP_200_OK)
