import ipaddress
import json
import logging

from django.contrib import messages
from django.db.models import Count
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods

from .forms import PilotCallForm
from .models import PilotCall, WebhookEvent


logger = logging.getLogger(__name__)
MAX_WEBHOOK_BODY_BYTES = 512 * 1024


def home(request):
    """Render the branded landing dashboard for the real-time intake pilot."""
    recent_calls = PilotCall.objects.all()[:3]
    total_calls = PilotCall.objects.count()
    urgent_count = sum(1 for call in PilotCall.objects.all() if call.urgent_flags)
    avg_completion = 0
    if total_calls:
        avg_completion = round(sum(call.completion_score for call in PilotCall.objects.all()) / total_calls)

    context = {
        'project_name': 'ReliefSignal',
        'meta_description': 'Real-time tax relief call intake pilot for live transcription, structured extraction, completion scoring, and CRM readiness.',
        'recent_calls': recent_calls,
        'total_calls': total_calls,
        'urgent_count': urgent_count,
        'avg_completion': avg_completion,
    }
    return render(request, 'core/index.html', context)


def call_list(request):
    calls = PilotCall.objects.all()
    summary = calls.aggregate(total=Count('id'))
    context = {
        'project_name': 'ReliefSignal',
        'meta_description': 'Review tax relief pilot calls, completion scores, and urgency flags.',
        'calls': calls,
        'total_calls': summary['total'] or 0,
    }
    return render(request, 'core/call_list.html', context)


def call_create(request):
    if request.method == 'POST':
        form = PilotCallForm(request.POST)
        if form.is_valid():
            call = form.save()
            messages.success(request, 'Pilot call saved. Review the completion score and missing intake fields below.')
            return redirect('call_detail', pk=call.pk)
    else:
        form = PilotCallForm()

    context = {
        'project_name': 'ReliefSignal',
        'meta_description': 'Create a tax relief pilot call intake from a live transcript segment.',
        'form': form,
    }
    return render(request, 'core/call_form.html', context)


def call_detail(request, pk):
    call = get_object_or_404(PilotCall, pk=pk)
    intake_fields = [
        ('IRS Debt', call.irs_debt),
        ('State Debt', call.state_debt),
        ('Tax Years', call.tax_years),
        ('Filing Status', call.filing_status),
        ('Employment', call.employment_type),
        ('Occupation', call.occupation),
        ('Monthly Gross Income', call.monthly_gross_income),
        ('Spouse Income', call.spouse_income),
        ('Mortgage', call.mortgage),
        ('Rent', call.rent),
        ('Utilities', call.utilities),
        ('Car Payment', call.car_payment),
        ('Insurance', call.insurance),
        ('Student Loans', call.student_loans),
        ('Credit Cards', call.credit_cards),
        ('Business Expenses', call.business_expenses),
        ('Best Callback Time', call.best_callback_time),
    ]
    context = {
        'project_name': 'ReliefSignal',
        'meta_description': f'Pilot call detail for {call.client_name}: transcript, extracted tax relief fields, urgency flags, and completion score.',
        'call': call,
        'intake_fields': intake_fields,
    }
    return render(request, 'core/call_detail.html', context)



def audio_capture(request):
    context = {
        'project_name': 'ReliefSignal',
        'meta_description': 'Browser-based computer audio capture prototype for microphone input, shared tab/system output audio, local level monitoring, and local recording tests.',
    }
    return render(request, 'core/audio_capture.html', context)


def webhook_list(request):
    events = WebhookEvent.objects.all()[:25]
    context = {
        'project_name': 'ReliefSignal',
        'meta_description': 'Provider-neutral webhook inbox for testing Ytel-style call events before a dedicated integration is finalized.',
        'events': events,
        'total_events': WebhookEvent.objects.count(),
        'endpoint_url': _public_absolute_uri(request, reverse('webhook_generic')),
    }
    return render(request, 'core/webhook_list.html', context)


@csrf_exempt
@require_http_methods(['GET', 'POST'])
def webhook_generic(request):
    if request.method == 'GET':
        return JsonResponse({
            'ok': True,
            'name': 'ReliefSignal generic webhook receiver',
            'usage': 'POST JSON or form payloads to this endpoint. Add ?provider=ytel later when testing Ytel deliveries.',
            'accepted_methods': ['POST'],
        })

    body = request.body or b''
    if len(body) > MAX_WEBHOOK_BODY_BYTES:
        logger.warning('Rejected oversized webhook body: %s bytes', len(body))
        return JsonResponse({'ok': False, 'error': 'Payload is too large.'}, status=413)

    content_type = request.META.get('CONTENT_TYPE', '')[:180]
    raw_body = body.decode('utf-8', errors='replace')
    payload, parsed = _parse_webhook_payload(request, raw_body, content_type)

    provider = _normalise_label(
        request.GET.get('provider')
        or request.headers.get('X-Webhook-Provider')
        or _payload_lookup(payload, 'provider', 'source'),
        fallback='generic',
        max_length=60,
    )
    event_type = _normalise_label(
        request.headers.get('X-Event-Type')
        or request.headers.get('X-Webhook-Event')
        or request.headers.get('X-Hook-Event')
        or request.headers.get('X-Ytel-Event-Type')
        or _payload_lookup(payload, 'event_type', 'eventType', 'event', 'type', 'action', 'status'),
        fallback='',
        max_length=120,
    )
    delivery_id = _normalise_label(
        request.headers.get('X-Delivery-Id')
        or request.headers.get('X-Webhook-Id')
        or request.headers.get('X-Request-Id')
        or request.headers.get('X-Event-Id')
        or request.headers.get('X-Ytel-Delivery-Id')
        or request.headers.get('X-Ytel-Event-Id')
        or request.headers.get('Cf-Ray'),
        fallback='',
        max_length=160,
    )
    external_id = _normalise_label(
        _payload_lookup(payload, 'external_id', 'externalId', 'event_id', 'eventId', 'id', 'call_id', 'callId', 'uuid', 'sid'),
        fallback='',
        max_length=160,
    )

    event = WebhookEvent.objects.create(
        provider=provider,
        event_type=event_type,
        external_id=external_id,
        delivery_id=delivery_id,
        source_ip=_source_ip(request),
        content_type=content_type,
        headers=_safe_request_headers(request),
        query_params=_query_params(request),
        payload=payload,
        raw_body=raw_body,
    )

    return JsonResponse({
        'ok': True,
        'accepted': True,
        'parsed': parsed,
        'id': event.pk,
        'provider': event.provider,
        'event_type': event.display_event_type,
        'received_at': event.received_at.isoformat(),
    }, status=202)


def _public_absolute_uri(request, path):
    url = request.build_absolute_uri(path)
    host = request.get_host().split(':')[0]
    if host not in ('127.0.0.1', 'localhost') and url.startswith('http://'):
        return 'https://' + url[len('http://'):]
    return url


def _parse_webhook_payload(request, raw_body, content_type):
    if raw_body and ('application/json' in content_type or raw_body.lstrip().startswith(('{', '['))):
        try:
            return json.loads(raw_body), True
        except json.JSONDecodeError:
            return {'_unparsed_preview': raw_body[:2000]}, False

    if request.POST:
        return _multi_value_dict(request.POST), True

    if raw_body:
        return {'_raw_preview': raw_body[:2000]}, False

    return {}, True


def _safe_request_headers(request):
    sensitive_markers = ('authorization', 'cookie', 'token', 'secret', 'key', 'signature')
    safe_headers = {}
    for name, value in request.headers.items():
        if any(marker in name.lower() for marker in sensitive_markers):
            safe_headers[name] = '[redacted]'
        else:
            safe_headers[name] = str(value)[:500]
    return safe_headers


def _query_params(request):
    return _multi_value_dict(request.GET)


def _multi_value_dict(query_dict):
    return {
        key: values if len(values := query_dict.getlist(key)) > 1 else query_dict.get(key)
        for key in query_dict
    }


def _payload_lookup(payload, *keys):
    if not isinstance(payload, dict):
        return None

    for key in keys:
        value = payload.get(key)
        if value not in (None, ''):
            return value

    for nested_key in ('data', 'event', 'call', 'payload'):
        nested = payload.get(nested_key)
        if isinstance(nested, dict):
            value = _payload_lookup(nested, *keys)
            if value not in (None, ''):
                return value

    return None


def _normalise_label(value, fallback='', max_length=120):
    if value in (None, ''):
        return fallback
    if isinstance(value, (dict, list)):
        value = json.dumps(value, sort_keys=True)
    return str(value).strip()[:max_length] or fallback


def _source_ip(request):
    raw_ip = (
        request.headers.get('Cf-Connecting-Ip')
        or request.headers.get('X-Real-Ip')
        or request.headers.get('X-Forwarded-For')
        or request.META.get('REMOTE_ADDR')
        or ''
    )
    candidate = raw_ip.split(',')[0].strip()
    try:
        ipaddress.ip_address(candidate)
    except ValueError:
        return None
    return candidate
