import json
from datetime import datetime, time
from functools import wraps
from io import BytesIO

from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.db.models import Count, Sum
from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_POST, require_http_methods
from django.utils import timezone

from .forms import BusinessProfileForm, LoginForm, ReportFilterForm, SignUpForm, TransactionForm
from .jwt_auth import (
    JWTAuthError,
    authenticate_authorization_header,
    authenticate_refresh_token,
    issue_token_pair,
    revoke_user_tokens,
)
from .models import BusinessProfile, Transaction


def get_api_authenticated_user(request):
    if request.user.is_authenticated:
        return request.user

    authorization = (request.META.get('HTTP_AUTHORIZATION') or '').strip()
    if not authorization:
        return None

    user, _ = authenticate_authorization_header(authorization)
    request.user = user
    return user


def api_login_required(view_func):
    @wraps(view_func)
    def wrapped(request, *args, **kwargs):
        try:
            user = get_api_authenticated_user(request)
        except JWTAuthError as exc:
            return JsonResponse({
                'ok': False,
                'error': exc.message,
                'code': exc.code,
            }, status=exc.status_code)

        if user is None:
            return JsonResponse({
                'ok': False,
                'error': 'Authentication required.',
                'code': 'auth_required',
            }, status=401)
        return view_func(request, *args, **kwargs)

    return wrapped


def serialize_transaction(entry):
    return {
        'id': entry.id,
        'client_name': entry.client_name,
        'amount': str(entry.amount),
        'transaction_type': entry.transaction_type,
        'transaction_type_label': entry.get_transaction_type_display(),
        'service_charge': str(entry.service_charge),
        'notes': entry.notes,
        'ecash_before': str(entry.ecash_before),
        'ecash_after': str(entry.ecash_after),
        'physical_before': str(entry.physical_before),
        'physical_after': str(entry.physical_after),
        'created_by': entry.created_by.username,
        'created_at': timezone.localtime(entry.created_at).isoformat(),
    }


def get_profile(user):
    profile, _ = BusinessProfile.objects.get_or_create(user=user)
    return profile


def build_report_snapshot(profile, params=None):
    form = ReportFilterForm(params or None)
    form.is_valid()
    start_date, end_date = form.get_range() if form.cleaned_data else (timezone.localdate(), timezone.localdate())
    start_dt = timezone.make_aware(datetime.combine(start_date, time.min))
    end_dt = timezone.make_aware(datetime.combine(end_date, time.max))
    entries = profile.transactions.filter(created_at__range=(start_dt, end_dt)).select_related('created_by')
    summary_rows = entries.values('transaction_type').annotate(
        total_amount=Sum('amount'),
        total_fees=Sum('service_charge'),
        total_count=Count('id'),
    )
    summary_map = {row['transaction_type']: row for row in summary_rows}
    ordered_summary = []
    for value, label in Transaction.TYPE_CHOICES:
        row = summary_map.get(value)
        ordered_summary.append({
            'key': value,
            'label': label,
            'count': row['total_count'] if row else 0,
            'amount': row['total_amount'] if row and row['total_amount'] else 0,
            'fees': row['total_fees'] if row and row['total_fees'] else 0,
        })
    totals = entries.aggregate(total_amount=Sum('amount'), total_fees=Sum('service_charge'), total_count=Count('id'))
    latest_entry = entries.order_by('-created_at', '-id').first()
    closing_ecash = latest_entry.ecash_after if latest_entry else profile.current_ecash
    closing_physical = latest_entry.physical_after if latest_entry else profile.current_physical_cash
    return {
        'form': form,
        'entries': entries.order_by('-created_at', '-id'),
        'summary': ordered_summary,
        'total_amount': totals['total_amount'] or 0,
        'total_fees': totals['total_fees'] or 0,
        'total_count': totals['total_count'] or 0,
        'start_date': start_date,
        'end_date': end_date,
        'closing_ecash': closing_ecash,
        'closing_physical': closing_physical,
    }


def home(request):
    context = {
        'page_title': 'MoMoLedger | Agent wallet dashboard',
        'meta_description': 'Manage MoMo transactions, balances, business branding, and daily-to-yearly reports from one polished dashboard.',
        'signup_form': SignUpForm(),
        'login_form': LoginForm(request=request),
    }
    if request.user.is_authenticated:
        profile = get_profile(request.user)
        report_snapshot = build_report_snapshot(profile, {'period': 'daily'})
        context.update({
            'profile': profile,
            'recent_transactions': profile.transactions.select_related('created_by')[:5],
            'today_total': report_snapshot['total_amount'],
            'today_fees': report_snapshot['total_fees'],
            'today_count': report_snapshot['total_count'],
        })
    return render(request, 'core/index.html', context)


def signup_view(request):
    if request.user.is_authenticated:
        return redirect('home')
    form = SignUpForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        user = form.save()
        BusinessProfile.objects.get_or_create(user=user)
        login(request, user)
        messages.success(request, 'Welcome! Your account is ready. Set your business details next.')
        return redirect('profile')
    return render(request, 'core/auth.html', {
        'form': form,
        'mode': 'signup',
        'page_title': 'Create your MoMo account',
        'meta_description': 'Create your mobile money business account and start tracking your balances securely.',
    })


def login_view(request):
    if request.user.is_authenticated:
        return redirect('home')
    form = LoginForm(request=request, data=request.POST or None)
    if request.method == 'POST' and form.is_valid():
        login(request, form.get_user())
        messages.success(request, 'Welcome back to your MoMo dashboard.')
        return redirect('home')
    return render(request, 'core/auth.html', {
        'form': form,
        'mode': 'login',
        'page_title': 'Log in to MoMoLedger',
        'meta_description': 'Access your mobile money dashboard, balances, transaction log, and reports.',
    })


@login_required
def logout_view(request):
    logout(request)
    messages.info(request, 'You have been logged out.')
    return redirect('home')


@login_required
def profile_view(request):
    profile = get_profile(request.user)
    form = BusinessProfileForm(request.POST or None, request.FILES or None, instance=profile)
    if request.method == 'POST' and form.is_valid():
        profile = form.save()
        messages.success(request, 'Business profile saved. Your branding will now appear on reports.')
        return redirect('profile')
    return render(request, 'core/profile_form.html', {
        'form': form,
        'profile': profile,
        'page_title': 'Business setup | MoMoLedger',
        'meta_description': 'Update your business name, logo, and opening wallet balances for branded reports.',
    })


@login_required
def transaction_create_view(request):
    profile = get_profile(request.user)
    if not profile.business_name:
        messages.info(request, 'Start by saving your business profile before logging transactions.')
        return redirect('profile')
    form = TransactionForm(request.POST or None, business=profile)
    if request.method == 'POST' and form.is_valid():
        entry = form.save(request.user)
        messages.success(request, 'Transaction recorded and balances updated automatically.')
        return redirect('transaction_detail', transaction_id=entry.id)
    return render(request, 'core/transaction_form.html', {
        'form': form,
        'profile': profile,
        'page_title': 'New transaction | MoMoLedger',
        'meta_description': 'Record cash-in, cash-out, sending, airtime, transfer, debt, expenditure, and credit in one place.',
    })


@login_required
def transaction_list_view(request):
    profile = get_profile(request.user)
    entries = profile.transactions.select_related('created_by')
    summary = entries.aggregate(total_amount=Sum('amount'), total_fees=Sum('service_charge'), total_count=Count('id'))
    return render(request, 'core/transaction_list.html', {
        'profile': profile,
        'transactions': entries,
        'summary': summary,
        'page_title': 'Transactions | MoMoLedger',
        'meta_description': 'Browse recent mobile money transactions and review balance movements.',
    })


@login_required
def transaction_detail_view(request, transaction_id):
    profile = get_profile(request.user)
    entry = get_object_or_404(profile.transactions.select_related('created_by'), id=transaction_id)
    return render(request, 'core/transaction_detail.html', {
        'profile': profile,
        'transaction': entry,
        'page_title': f'{entry.get_transaction_type_display()} details | MoMoLedger',
        'meta_description': 'Inspect one MoMo transaction and see the exact e-cash and physical cash balance effect.',
    })


@login_required
def reports_view(request):
    profile = get_profile(request.user)
    snapshot = build_report_snapshot(profile, request.GET or {'period': 'daily'})
    context = {
        'profile': profile,
        'page_title': 'Reports | MoMoLedger',
        'meta_description': 'View printable MoMo business reports by day, week, month, year, or a custom date range.',
        **snapshot,
    }
    return render(request, 'core/reports.html', context)


@login_required
def report_pdf_view(request):
    try:
        from reportlab.lib import colors
        from reportlab.lib.pagesizes import A4
        from reportlab.lib.units import cm
        from reportlab.pdfbase.pdfmetrics import stringWidth
        from reportlab.pdfgen import canvas
    except Exception as exc:  # pragma: no cover
        raise Http404('PDF support is not available.') from exc

    profile = get_profile(request.user)
    snapshot = build_report_snapshot(profile, request.GET or {'period': 'daily'})

    buffer = BytesIO()
    pdf = canvas.Canvas(buffer, pagesize=A4)
    width, height = A4
    margin = 1.5 * cm
    y = height - margin

    pdf.setTitle('MoMo report')
    pdf.setFillColor(colors.HexColor('#0f6f5c'))
    pdf.rect(0, height - 4 * cm, width, 4 * cm, fill=1, stroke=0)
    pdf.setFillColor(colors.white)
    pdf.setFont('Helvetica-Bold', 18)
    pdf.drawString(margin, height - 1.5 * cm, profile.business_name or 'MoMoLedger Business')
    pdf.setFont('Helvetica', 10)
    owner_text = f"Owner: {profile.owner_label} | User: {request.user.username} | Email: {request.user.email or 'Not set'}"
    pdf.drawString(margin, height - 2.2 * cm, owner_text[:100])
    pdf.drawString(margin, height - 2.8 * cm, f"Report window: {snapshot['start_date']} to {snapshot['end_date']}")

    if profile.logo:
        try:
            pdf.drawImage(profile.logo.path, width - 4.5 * cm, height - 3.2 * cm, width=2.5 * cm, height=2.5 * cm, preserveAspectRatio=True, mask='auto')
        except Exception:
            pass

    y = height - 5.2 * cm
    pdf.setFillColor(colors.HexColor('#143642'))
    pdf.setFont('Helvetica-Bold', 12)
    pdf.drawString(margin, y, 'Balance summary')
    y -= 0.6 * cm
    pdf.setFont('Helvetica', 10)
    metrics = [
        f"Opening e-cash: {profile.opening_ecash}",
        f"Opening physical: {profile.opening_physical_cash}",
        f"Closing e-cash: {snapshot['closing_ecash']}",
        f"Closing physical: {snapshot['closing_physical']}",
        f"Transactions: {snapshot['total_count']}",
        f"Gross amount: {snapshot['total_amount']}",
        f"Service fees: {snapshot['total_fees']}",
    ]
    for item in metrics:
        pdf.drawString(margin, y, item)
        y -= 0.45 * cm

    y -= 0.2 * cm
    pdf.setFont('Helvetica-Bold', 12)
    pdf.drawString(margin, y, 'Transaction type totals')
    y -= 0.6 * cm
    pdf.setFont('Helvetica-Bold', 10)
    pdf.drawString(margin, y, 'Type')
    pdf.drawString(8.2 * cm, y, 'Count')
    pdf.drawString(11 * cm, y, 'Amount')
    pdf.drawString(15 * cm, y, 'Fees')
    y -= 0.35 * cm
    pdf.setStrokeColor(colors.HexColor('#d4dfd6'))
    pdf.line(margin, y, width - margin, y)
    y -= 0.45 * cm
    pdf.setFont('Helvetica', 10)
    for row in snapshot['summary']:
        if y < 3 * cm:
            pdf.showPage()
            y = height - margin
        pdf.drawString(margin, y, row['label'])
        pdf.drawString(8.2 * cm, y, str(row['count']))
        pdf.drawString(11 * cm, y, str(row['amount']))
        pdf.drawString(15 * cm, y, str(row['fees']))
        y -= 0.45 * cm

    y -= 0.3 * cm
    pdf.setFont('Helvetica-Bold', 12)
    pdf.drawString(margin, y, 'Recent entries')
    y -= 0.6 * cm
    pdf.setFont('Helvetica', 9)
    for entry in snapshot['entries'][:12]:
        if y < 2.5 * cm:
            pdf.showPage()
            y = height - margin
        label = f"{timezone.localtime(entry.created_at).strftime('%Y-%m-%d %H:%M')} · {entry.get_transaction_type_display()} · {entry.client_name} · {entry.amount}"
        max_width = width - (2 * margin)
        while stringWidth(label, 'Helvetica', 9) > max_width and len(label) > 3:
            label = label[:-4] + '...'
        pdf.drawString(margin, y, label)
        y -= 0.42 * cm

    pdf.showPage()
    pdf.save()
    buffer.seek(0)
    filename = f"momo-report-{snapshot['start_date']}-to-{snapshot['end_date']}.pdf"
    return FileResponse(buffer, as_attachment=True, filename=filename)


def parse_api_payload(request):
    if (request.content_type or '').startswith('application/json'):
        try:
            return json.loads(request.body.decode('utf-8') or '{}')
        except (json.JSONDecodeError, UnicodeDecodeError):
            return None
    return request.POST


def serialize_form_errors(form):
    errors = {}
    for field, items in form.errors.get_json_data().items():
        errors[field] = [item['message'] for item in items]
    return errors


def build_transaction_summary(profile):
    summary = profile.transactions.aggregate(
        total_amount=Sum('amount'),
        total_fees=Sum('service_charge'),
        total_count=Count('id'),
    )
    return {
        'total_amount': str(summary['total_amount'] or 0),
        'total_fees': str(summary['total_fees'] or 0),
        'total_count': summary['total_count'] or 0,
    }


@require_GET
def api_health_view(request):
    return JsonResponse({
        'ok': True,
        'app': 'MoMoLedger API',
        'message': 'Android backend starter endpoints are available.',
        'server_time': timezone.now().isoformat(),
        'authenticated': bool(request.user.is_authenticated or (request.META.get('HTTP_AUTHORIZATION') or '').strip()),
    })


@csrf_exempt
@require_POST
def api_login_view(request):
    user = request.user if request.user.is_authenticated else None

    if user is None:
        payload = parse_api_payload(request)
        if payload is None:
            return JsonResponse({
                'ok': False,
                'error': 'Invalid JSON body.',
            }, status=400)

        username = (payload.get('username') or '').strip()
        password = payload.get('password') or ''

        if not username or not password:
            return JsonResponse({
                'ok': False,
                'error': 'Username and password are required.',
            }, status=400)

        user = authenticate(request, username=username, password=password)
        if user is None:
            return JsonResponse({
                'ok': False,
                'error': 'Invalid username or password.',
            }, status=401)

        login(request, user)
        message = 'Login successful.'
    else:
        message = 'Already logged in.'

    profile = get_profile(user)
    tokens = issue_token_pair(user)
    return JsonResponse({
        'ok': True,
        'message': message,
        'user': {
            'id': user.id,
            'username': user.username,
            'email': user.email,
            'business_name': profile.business_name,
        },
        'tokens': tokens,
    })


@csrf_exempt
@require_POST
def api_token_refresh_view(request):
    payload = parse_api_payload(request)
    if payload is None:
        return JsonResponse({
            'ok': False,
            'error': 'Invalid JSON body.',
        }, status=400)

    refresh_token = (payload.get('refresh_token') or payload.get('refresh') or '').strip()
    try:
        user, profile = authenticate_refresh_token(refresh_token)
    except JWTAuthError as exc:
        return JsonResponse({
            'ok': False,
            'error': exc.message,
            'code': exc.code,
        }, status=exc.status_code)

    return JsonResponse({
        'ok': True,
        'message': 'Token refreshed successfully.',
        'user': {
            'id': user.id,
            'username': user.username,
            'email': user.email,
            'business_name': profile.business_name,
        },
        'tokens': issue_token_pair(user),
    })


@csrf_exempt
@require_POST
def api_logout_view(request):
    payload = parse_api_payload(request)
    if payload is None:
        return JsonResponse({
            'ok': False,
            'error': 'Invalid JSON body.',
        }, status=400)

    user = request.user if request.user.is_authenticated else None
    refresh_token = (payload.get('refresh_token') or payload.get('refresh') or '').strip()

    if user is None and refresh_token:
        try:
            user, _ = authenticate_refresh_token(refresh_token)
        except JWTAuthError as exc:
            return JsonResponse({
                'ok': False,
                'error': exc.message,
                'code': exc.code,
            }, status=exc.status_code)

    if user is None:
        authorization = (request.META.get('HTTP_AUTHORIZATION') or '').strip()
        if authorization:
            try:
                user, _ = authenticate_authorization_header(authorization)
            except JWTAuthError as exc:
                return JsonResponse({
                    'ok': False,
                    'error': exc.message,
                    'code': exc.code,
                }, status=exc.status_code)

    if user is None:
        return JsonResponse({
            'ok': True,
            'message': 'No active session.',
        })

    revoke_user_tokens(user)
    if request.user.is_authenticated:
        logout(request)

    return JsonResponse({
        'ok': True,
        'message': 'Logout successful. Mobile tokens revoked.',
    })


@require_GET
@api_login_required
def api_profile_view(request):
    profile = get_profile(request.user)
    return JsonResponse({
        'ok': True,
        'profile': {
            'username': request.user.username,
            'email': request.user.email,
            'owner_label': profile.owner_label,
            'business_name': profile.business_name,
            'opening_ecash': str(profile.opening_ecash),
            'opening_physical_cash': str(profile.opening_physical_cash),
            'current_ecash': str(profile.current_ecash),
            'current_physical_cash': str(profile.current_physical_cash),
            'total_cash': str(profile.total_cash),
            'created_at': timezone.localtime(profile.created_at).isoformat(),
            'updated_at': timezone.localtime(profile.updated_at).isoformat(),
        },
    })


@csrf_exempt
@api_login_required
@require_http_methods(['GET', 'POST'])
def api_transactions_view(request):
    profile = get_profile(request.user)

    if request.method == 'POST':
        payload = parse_api_payload(request)
        if payload is None:
            return JsonResponse({
                'ok': False,
                'error': 'Invalid JSON body.',
            }, status=400)

        form = TransactionForm(payload or None, business=profile)
        if not form.is_valid():
            return JsonResponse({
                'ok': False,
                'error': 'Validation failed.',
                'errors': serialize_form_errors(form),
            }, status=400)

        try:
            entry = form.save(request.user)
        except ValidationError as exc:
            return JsonResponse({
                'ok': False,
                'error': exc.messages[0] if exc.messages else 'Could not create transaction.',
            }, status=400)

        profile.refresh_from_db(fields=['current_ecash', 'current_physical_cash'])
        return JsonResponse({
            'ok': True,
            'message': 'Transaction created successfully.',
            'transaction': serialize_transaction(entry),
            'balances': {
                'current_ecash': str(profile.current_ecash),
                'current_physical_cash': str(profile.current_physical_cash),
                'total_cash': str(profile.total_cash),
            },
            'summary': build_transaction_summary(profile),
        }, status=201)

    try:
        limit = int(request.GET.get('limit', 20))
    except (TypeError, ValueError):
        limit = 20
    limit = max(1, min(limit, 100))

    entries = list(profile.transactions.select_related('created_by')[:limit])
    return JsonResponse({
        'ok': True,
        'count': len(entries),
        'limit': limit,
        'summary': build_transaction_summary(profile),
        'transactions': [serialize_transaction(entry) for entry in entries],
    })


@csrf_exempt
@api_login_required
@require_http_methods(['GET', 'PUT', 'PATCH', 'DELETE'])
def api_transaction_detail_view(request, transaction_id):
    profile = get_profile(request.user)
    entry = get_object_or_404(profile.transactions.select_related('created_by'), id=transaction_id)

    if request.method == 'GET':
        return JsonResponse({
            'ok': True,
            'transaction': serialize_transaction(entry),
            'balances': {
                'current_ecash': str(profile.current_ecash),
                'current_physical_cash': str(profile.current_physical_cash),
                'total_cash': str(profile.total_cash),
            },
            'summary': build_transaction_summary(profile),
        })

    if request.method in {'PUT', 'PATCH'}:
        payload = parse_api_payload(request)
        if payload is None:
            return JsonResponse({
                'ok': False,
                'error': 'Invalid JSON body.',
            }, status=400)

        merged_payload = {
            'client_name': entry.client_name,
            'amount': str(entry.amount),
            'transaction_type': entry.transaction_type,
            'notes': entry.notes,
        }
        merged_payload.update(payload)
        form = TransactionForm(merged_payload, business=profile, instance=entry)
        if not form.is_valid():
            return JsonResponse({
                'ok': False,
                'error': 'Validation failed.',
                'errors': serialize_form_errors(form),
            }, status=400)

        try:
            update_result = form.save(request.user)
        except ValidationError as exc:
            return JsonResponse({
                'ok': False,
                'error': exc.messages[0] if exc.messages else 'Could not update transaction.',
            }, status=400)

        profile = update_result['business']
        entry = update_result['transaction']
        return JsonResponse({
            'ok': True,
            'message': 'Transaction updated successfully.',
            'transaction': serialize_transaction(entry),
            'balances': {
                'current_ecash': str(profile.current_ecash),
                'current_physical_cash': str(profile.current_physical_cash),
                'total_cash': str(profile.total_cash),
            },
            'summary': build_transaction_summary(profile),
        })

    deleted_transaction = serialize_transaction(entry)
    try:
        delete_result = entry.delete_logged_transaction()
    except ValidationError as exc:
        return JsonResponse({
            'ok': False,
            'error': exc.messages[0] if exc.messages else 'Could not delete transaction.',
        }, status=400)

    profile = delete_result['business']
    return JsonResponse({
        'ok': True,
        'message': 'Transaction deleted successfully.',
        'deleted_transaction': deleted_transaction,
        'deleted_transaction_id': delete_result['transaction_id'],
        'balances': {
            'current_ecash': str(profile.current_ecash),
            'current_physical_cash': str(profile.current_physical_cash),
            'total_cash': str(profile.total_cash),
        },
        'summary': build_transaction_summary(profile),
    })
