from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from django.utils.timezone import now, timedelta
from django.conf import settings

import json, uuid, base64, gzip, jwt, time, re, traceback, io
from openpyxl import Workbook
from openpyxl.drawing.image import Image as XLImage

from accounts.models import Organization, PlatformSettings, VERIFICATION_FIELD_LABELS, default_verification_fields
from .models import Visitor, VisitorSession, CallbackLog


# ─── GENERATE QR ───────────────────────────────────────────────
@login_required(login_url='/login/')
def generate_qr(request):
    if request.method != 'POST':
        return JsonResponse({'error': 'Invalid request'}, status=400)

    if request.headers.get('x-requested-with') != 'XMLHttpRequest':
        return JsonResponse({'error': 'Direct access blocked'}, status=403)

    org = request.user.org
    if not org:
        return JsonResponse({'error': 'No organization found'}, status=400)

    if not org.uidai_setup_done:
        return JsonResponse({'error': 'UIDAI setup not complete. Go to Settings.'}, status=400)

    platform = PlatformSettings.get_solo()
    if not platform.is_uidai_ready():
        return JsonResponse({'error': 'Platform UIDAI credentials not configured. Contact support.'}, status=400)

    if not org.can_verify():
        return JsonResponse({'error': f'Monthly limit reached. Upgrade your plan.'}, status=429)

    try:
        ip     = request.META.get('REMOTE_ADDR', '')
        ua     = request.META.get('HTTP_USER_AGENT', '')
        now_ts = int(time.time())
        txn_id = str(uuid.uuid4())

        # Cleanup expired sessions for this org
        VisitorSession.objects.filter(
            org=org,
            status__in=['pending', 'scanned', 'processing'],
            expires_at__lt=now()
        ).update(status='expired')

        # A fresh "Generate QR" click always supersedes any old, still-active
        # session for this org (e.g. one the person opened, closed without
        # scanning, then re-opened) — auto-cancel it instead of blocking,
        # so a closed-and-reopened QR never gets stuck.
        VisitorSession.objects.filter(
            org=org,
            status__in=['pending', 'scanned', 'processing'],
            expires_at__gt=now()
        ).update(status='cancelled')

        # Create session
        VisitorSession.objects.create(
            org        = org,
            session_id = txn_id,
            status     = 'pending',
            ip_address = ip,
            user_agent = ua,
            expires_at = now() + timedelta(seconds=300),
        )

        # ── JWT Payload (UIDAI Spec Section 3.1.2) ──
        # Bitmap is built from this org's own chosen verification_fields —
        # only the data they actually want is requested from UIDAI.
        sc = org.build_sc_bitmap()

        payload = {
            'txn':  txn_id,
            'i':    'credential',
            'lang': '23',
            'sc':   sc,
            'pop':  1,
            'ch':   'qr',
            'm':    '1',
            'ac':   platform.ac_code,
            'sa':   platform.sa_code,
            'cb':   org.callback_url,
            'aud':  platform.ovse_base_url.rstrip('/'),   # must match cb's domain, per UIDAI spec
            'iss':  'https://uidai.gov.in',
            'exp':  now_ts + 300,
            'iat':  now_ts,
            'ht':   '',
            'aid':  None,
            'asig': None,
            'jti':  str(uuid.uuid4()),
        }

        # Step 1 — Sign JWT
        token   = jwt.encode(payload, platform.private_key, algorithm='RS256')
        jwt_str = token if isinstance(token, str) else token.decode()

        # Step 2 — ISO-8859-1 encode
        iso_bytes = jwt_str.encode('iso-8859-1')

        # Step 3 — Append delimiter 255
        iso_bytes += bytes([255])

        # Step 4 — Gzip compress
        gzipped = gzip.compress(iso_bytes)

        # Step 5 & 6 — BigInt → Base10
        base10 = str(int.from_bytes(gzipped, byteorder='big'))

        # Step 7 — Build URL
        qr_url = f"https://maadhaar.com/getIntent?value={base10}"

        # Step 8 — Generate QR image
        import qrcode
        from io import BytesIO

        qr = qrcode.QRCode(
            version=None,
            error_correction=qrcode.constants.ERROR_CORRECT_H,
            box_size=12,
            border=4,
        )
        qr.add_data(qr_url)
        qr.make(fit=True)

        img    = qr.make_image(fill_color='black', back_color='white')
        buf    = BytesIO()
        img.save(buf, format='PNG')
        img_b64 = base64.b64encode(buf.getvalue()).decode()

        return JsonResponse({
            'qr':         f"data:image/png;base64,{img_b64}",
            'session':    txn_id,
            'expires_in': 300,
        })

    except Exception as e:
        print('GENERATE_QR ERROR:', traceback.format_exc())
        return JsonResponse({'error': str(e)}, status=500)


# ─── VERIFY RESPONSE (UIDAI CALLBACK) ──────────────────────────
@csrf_exempt
def verify_response(request, *args, **kwargs):
    """
    UIDAI calls this single, fixed endpoint after every QR scan —
    the SAME URL for every client (https://<ovse-base>/verify-response/).
    UIDAI has no idea which client this belongs to, so we resolve the
    org by looking up the TxnID (in the body) against the VisitorSession
    that generate_qr() created for that org.
    """
    try:
        raw_body = request.body
    except Exception:
        raw_body = b''

    body_str = raw_body.decode('utf-8', errors='replace')

    if request.method != 'POST':
        return HttpResponse(
            '<Response><TxnID></TxnID><ResponseCode>400</ResponseCode>'
            '<ResponseMsg>Only POST allowed</ResponseMsg></Response>',
            content_type='application/xml', status=400
        )

    try:
        # Parse XML — TxnID first, since it's how we find the org
        txn_match  = re.search(r'<TxnID>(.*?)</TxnID>',           body_str, re.DOTALL)
        cred_match = re.search(r'<Credential>(.*?)</Credential>',  body_str, re.DOTALL)

        if not txn_match:
            CallbackLog.objects.create(
                org=None, endpoint='/verify-response/', method=request.method,
                body=body_str[:8000], headers=str(dict(request.headers))[:1000],
                ip_address=request.META.get('REMOTE_ADDR', ''),
            )
            return HttpResponse(
                '<Response><TxnID></TxnID><ResponseCode>400</ResponseCode>'
                '<ResponseMsg>TxnID missing</ResponseMsg></Response>',
                content_type='application/xml', status=400
            )

        txn        = txn_match.group(1).strip()
        credential = cred_match.group(1).strip() if cred_match else None

        # Resolve org purely from the TxnID — this is the only way we
        # know which client this callback belongs to, since the URL
        # itself is identical for everyone.
        session = VisitorSession.objects.filter(session_id=txn).select_related('org').first()

        if not session:
            CallbackLog.objects.create(
                org=None, endpoint='/verify-response/', method=request.method,
                body=body_str[:8000], headers=str(dict(request.headers))[:1000],
                ip_address=request.META.get('REMOTE_ADDR', ''),
            )
            return HttpResponse(
                f'<Response><TxnID>{txn}</TxnID><ResponseCode>404</ResponseCode>'
                '<ResponseMsg>Unknown TxnID / session</ResponseMsg></Response>',
                content_type='application/xml', status=404
            )

        org = session.org

        # Log callback now that we know the org
        try:
            CallbackLog.objects.create(
                org        = org,
                endpoint   = '/verify-response/',
                method     = request.method,
                body       = body_str[:8000],
                headers    = str(dict(request.headers))[:1000],
                ip_address = request.META.get('REMOTE_ADDR', ''),
            )
        except Exception as log_err:
            print('CallbackLog error:', log_err)

        session.status     = 'scanned'
        session.scanned_at = now()
        session.save()

        # Parse SD-JWT
        name    = 'Unknown'
        dob     = ''
        gender  = ''
        address = ''
        extra   = {}   # any other org-chosen fields: mobile, email, pincode, careOf, ageAbove18, photo...

        if credential:
            try:
                parts    = credential.split('~')
                jwt_part = parts[0].strip()

                decoded = jwt.decode(
                    jwt_part,
                    algorithms=['RS256'],
                    options={'verify_signature': False},
                )

                name    = decoded.get('name',    decoded.get('sub', 'Unknown'))
                dob     = decoded.get('dob',     decoded.get('birthdate', ''))
                gender  = decoded.get('gender',  '')
                address = decoded.get('address', '')

                # Parse disclosures
                for disc in parts[1:]:
                    disc = disc.strip()
                    if not disc:
                        continue
                    try:
                        pad  = disc + '=' * (4 - len(disc) % 4)
                        d    = json.loads(base64.urlsafe_b64decode(pad).decode())
                        if isinstance(d, list) and len(d) == 3:
                            k_lower = d[1].lower()
                            v       = d[2]
                            if k_lower in ('residentname', 'name', 'fullname'):
                                name = v
                            elif k_lower in ('dob', 'birthdate'):
                                dob = v
                            elif k_lower in ('gender', 'sex'):
                                gender = v
                            elif k_lower in ('address', 'addr', 'residentaddress'):
                                address = v
                            elif k_lower in ('mobile',):
                                extra['mobile'] = v
                            elif k_lower in ('maskedmobile',):
                                extra['maskedMobile'] = v
                            elif k_lower in ('email',):
                                extra['email'] = v
                            elif k_lower in ('maskedemail',):
                                extra['maskedEmail'] = v
                            elif k_lower in ('pincode',):
                                extra['pincode'] = v
                            elif k_lower in ('careof', 'c/o'):
                                extra['careOf'] = v
                            elif k_lower in ('ageabove18',):
                                extra['ageAbove18'] = v
                            elif k_lower in ('residentimage', 'photo'):
                                extra['residentImage'] = v
                    except Exception:
                        pass

            except Exception as je:
                print('SD-JWT error:', je)

        # Only keep data this org actually asked for — even if UIDAI
        # sends more, we don't store fields outside the org's own choice.
        requested = set(default_verification_fields())
        if 'residentName' not in requested:
            name = name or 'Unknown'
        if 'dob' not in requested:
            dob = ''
        if 'gender' not in requested:
            gender = ''
        if 'address' not in requested:
            address = ''
        extra = {k: v for k, v in extra.items() if k in requested}

        # Save session
        session.name       = name
        session.dob        = dob
        session.extra_data = extra
        session.status     = 'verified'
        session.save()

        # Save Visitor record
        try:
            Visitor.objects.create(
                org        = org,
                name       = name,
                dob        = dob,
                gender     = gender,
                address    = address,
                extra_data = extra,
                ip_address = request.META.get('REMOTE_ADDR'),
                user_agent = request.META.get('HTTP_USER_AGENT', '')[:300],
            )
            # Increment verification count
            org.increment_verif_count()
        except Exception as ve:
            print('Visitor save error:', ve)

        # ---- Library/Coaching module: match/create Student + record attendance ----
        try:
            from students.models import Student, Attendance

            student = None
            if name and dob:
                student = Student.objects.filter(org=org, name__iexact=name, dob=dob).first()

            if not student and name:
                student = Student.objects.create(
                    org           = org,
                    name          = name,
                    dob           = dob,
                    gender        = gender,
                    address       = address,
                    phone         = extra.get('mobile', ''),
                    photo_base64  = extra.get('residentImage', ''),
                    added_via_qr  = True,
                )

            if student:
                Attendance.record_scan(org, student)

        except Exception as att_err:
            print('Student/Attendance error:', att_err)

        # Acknowledgement
        ack = f"""<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <TxnID>{txn}</TxnID>
  <ResponseCode>200</ResponseCode>
  <ResponseMsg>Success</ResponseMsg>
</Response>"""
        return HttpResponse(ack, content_type='application/xml', status=200)

    except Exception as e:
        print('VERIFY_RESPONSE ERROR:', traceback.format_exc())
        return HttpResponse(
            f'<Response><TxnID></TxnID><ResponseCode>307</ResponseCode>'
            f'<ResponseMsg>{str(e)[:100]}</ResponseMsg></Response>',
            content_type='application/xml', status=500
        )


# ─── CHECK STATUS ──────────────────────────────────────────────
@login_required(login_url='/login/')
def check_status(request):
    session_id = request.GET.get('session')
    org        = request.user.org

    if not session_id or not org:
        return JsonResponse({'status': 'invalid'})

    try:
        session = VisitorSession.objects.get(session_id=session_id, org=org)

        if session.is_expired() and session.status not in ('verified', 'failed'):
            session.status = 'expired'
            session.save(update_fields=['status'])
            return JsonResponse({'status': 'expired'})

        if session.status == 'verified':
            elapsed = (now() - session.scanned_at).total_seconds() if session.scanned_at else 10
            if elapsed < 2:
                return JsonResponse({'status': 'scanned'})
            if elapsed < 4:
                return JsonResponse({'status': 'processing'})
            return JsonResponse({
                'status': 'verified',
                'name':   session.name,
                'dob':    session.dob,
                'extra':  session.extra_data,
            })

        return JsonResponse({'status': session.status})

    except VisitorSession.DoesNotExist:
        return JsonResponse({'status': 'invalid'})


# ─── CANCEL SESSION ────────────────────────────────────────────
@csrf_exempt
@login_required(login_url='/login/')
def cancel_session(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'error'}, status=400)
    try:
        data       = json.loads(request.body)
        session_id = data.get('session', '')
        org        = request.user.org
        if session_id and org:
            VisitorSession.objects.filter(
                session_id=session_id,
                org=org,
                status__in=['pending', 'scanned', 'processing']
            ).update(status='cancelled')
        return JsonResponse({'status': 'cancelled'})
    except Exception as e:
        return JsonResponse({'status': 'error'}, status=500)


# ─── EXPORT EXCEL ──────────────────────────────────────────────
@login_required(login_url='/login/')
def export_excel(request):
    org = request.user.org
    if not org:
        return redirect('dashboard')

    if not org.can_export():
        return HttpResponse('Export not available on your plan. Please upgrade.', status=403)

    from_date = request.GET.get('from_date', '')
    to_date   = request.GET.get('to_date', '')

    visitors = Visitor.objects.filter(org=org).order_by('-created_at')
    if from_date:
        visitors = visitors.filter(created_at__date__gte=from_date)
    if to_date:
        visitors = visitors.filter(created_at__date__lte=to_date)

    wb = Workbook()
    ws = wb.active
    ws.title = 'Visitors'

    core_fields  = {'residentName', 'dob', 'gender', 'address'}
    extra_fields = [f for f in default_verification_fields() if f not in core_fields]
    extra_labels = [VERIFICATION_FIELD_LABELS.get(f, f) for f in extra_fields]

    ws.append(['Name', 'DOB', 'Gender', 'Address'] + extra_labels + ['Time'])

    # Photo goes in as an embedded thumbnail image, not raw base64 text
    photo_col_idx = None
    if 'residentImage' in extra_fields:
        photo_col_idx = 5 + extra_fields.index('residentImage')  # 1-indexed column

    for row_idx, v in enumerate(visitors, start=2):
        row = [v.name, v.dob, v.gender, v.address]
        for f in extra_fields:
            row.append('' if f == 'residentImage' else v.extra_data.get(f, ''))
        row.append(str(v.created_at))
        ws.append(row)

        if photo_col_idx:
            img_data = v.extra_data.get('residentImage')
            if img_data:
                try:
                    img_bytes  = base64.b64decode(img_data)
                    xl_img     = XLImage(io.BytesIO(img_bytes))
                    xl_img.width, xl_img.height = 40, 40
                    col_letter = ws.cell(row=row_idx, column=photo_col_idx).column_letter
                    ws.add_image(xl_img, f'{col_letter}{row_idx}')
                    ws.row_dimensions[row_idx].height = 32
                except Exception:
                    pass  # skip embedding on any bad/corrupt image data

    fname = f"visitors_{from_date or 'all'}_{to_date or ''}.xlsx".strip('_') + '.xlsx'

    response = HttpResponse(
        content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    )
    response['Content-Disposition'] = f'attachment; filename={fname}'
    wb.save(response)
    return response
