from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import transaction
from django.shortcuts import render, redirect
from django.utils import timezone
from .models import Wallet, Transfer, Withdrawal, AuditLog
from .forms import RegisterForm, SendForm, AmountForm, WithdrawalForm, PayloadTransferForm
from .services import post_transfer, simulate_deposit, simulate_withdrawal, display_amount, minor_units, create_tron_usdt_withdrawal, refresh_tron_withdrawal

def register(request):
    form = RegisterForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        user = User.objects.create_user(form.cleaned_data["username"],
            form.cleaned_data["email"], form.cleaned_data["password"])
        Wallet.objects.get_or_create(user=user, asset="TZS")
        Wallet.objects.get_or_create(user=user, asset="USDT")
        login(request, user)
        return redirect("dashboard")
    return render(request, "register.html", {"form": form})

def login_view(request):
    error = None
    if request.method == "POST":
        user = authenticate(request, username=request.POST.get("username", ""),
                            password=request.POST.get("password", ""))
        if user:
            login(request, user)
            return redirect("dashboard")
        error = "Invalid username or password."
    return render(request, "login.html", {"error": error})

def logout_view(request):
    if request.method == "POST":
        logout(request)
    return redirect("login")

@login_required
def dashboard(request):
    wallets = Wallet.objects.filter(user=request.user)
    for w in wallets:
        w.display_balance = display_amount(w.asset, w.balance_minor)
    return render(request, "dashboard.html", {"wallets": wallets})

@login_required
def deposit(request):
    form = AmountForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        try:
            amount = form.amount_minor()
            simulate_deposit(request.user, form.cleaned_data["asset"], amount)
            messages.success(request, "Simulated test funds added. These are not real money.")
            return redirect("dashboard")
        except ValidationError as e:
            form.add_error(None, e)
    return render(request, "form.html", {"form": form, "title": "Add test funds", "button": "Add simulated funds"})

@login_required
def send_money(request):
    form = SendForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        try:
            recipient = User.objects.get(username__iexact=form.cleaned_data["recipient_username"])
            amount = form.amount_minor()
            tx = post_transfer(request.user, recipient, form.cleaned_data["asset"], amount)
            messages.success(request, f"Transfer completed. Reference: {tx.reference}")
            return redirect("history")
        except User.DoesNotExist:
            form.add_error("recipient_username", "Recipient not found.")
        except (ValidationError, Wallet.DoesNotExist) as e:
            form.add_error(None, e)
    return render(request, "form.html", {"form": form, "title": "Send money", "button": "Send"})

@login_required
def withdraw(request):
    form = WithdrawalForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        try:
            amount = form.amount_minor()
            asset = form.cleaned_data["asset"]
            channel = form.cleaned_data["channel"]
            destination = form.cleaned_data["destination"]
            network = form.cleaned_data.get("network", "")
            if channel == Withdrawal.BLOCKCHAIN:
                if asset != Wallet.USDT or network != "Tron-Shasta":
                    raise ValidationError("Blockchain withdrawals currently support USDT on TRON Shasta only.")
                # A fresh browser request gets a unique idempotency key; API clients
                # should supply their own stable key.
                import uuid
                withdrawal = create_tron_usdt_withdrawal(
                    request.user, amount, destination,
                    idempotency_key=f"web-{uuid.uuid4().hex}",
                )
                messages.success(
                    request,
                    f"TRON testnet transaction submitted. Hash: {withdrawal.blockchain_tx_hash}. "
                    f"Status: {withdrawal.get_status_display()}."
                )
            else:
                withdrawal = simulate_withdrawal(
                    request.user, asset, amount, channel, destination, network,
                )
                messages.success(request, f"SIMULATION ONLY: bank withdrawal recorded. Reference: {withdrawal.reference}")
            return redirect("history")
        except (ValidationError, Wallet.DoesNotExist) as e:
            form.add_error(None, e)
    return render(request, "form.html", {
        "form": form,
        "title": "Withdraw",
        "button": "Send withdrawal",
    })

@login_required
def refresh_withdrawal(request, reference):
    if request.method != "POST":
        return HttpResponseNotAllowed(["POST"])
    withdrawal = Withdrawal.objects.filter(reference=reference, user=request.user).first()
    if withdrawal is None:
        return HttpResponseNotFound("Withdrawal not found.")
    try:
        refresh_tron_withdrawal(withdrawal)
        messages.success(request, f"Withdrawal status refreshed: {withdrawal.get_status_display()}.")
    except ValidationError as exc:
        messages.error(request, str(exc))
    return redirect("history")

@login_required
def history(request):
    sent = Transfer.objects.filter(sender=request.user).select_related("recipient", "transaction")
    received = Transfer.objects.filter(recipient=request.user).select_related("sender", "transaction")
    withdrawals = Withdrawal.objects.filter(user=request.user)
    return render(request, "history.html", {"sent": sent, "received": received, "withdrawals": withdrawals})


# Terminal configuration UI and JSON API (prototype only).
import json
from django.http import JsonResponse, HttpResponseNotAllowed, HttpResponseNotFound
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.utils.crypto import constant_time_compare
import hashlib
import hmac
import os
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_protect
from .models import Terminal, ProviderEvent
from .forms import TerminalForm

@login_required
@require_http_methods(["GET", "POST"])
def terminal_settings(request):
    terminal = Terminal.objects.filter(owner=request.user).first()
    if request.method == "POST":
        form = TerminalForm(request.POST, instance=terminal)
        if form.is_valid():
            obj = form.save(commit=False)
            obj.owner = request.user
            obj.full_clean()
            obj.save()
            messages.success(request, "Terminal configuration saved.")
            return redirect("terminal_settings")
    else:
        form = TerminalForm(instance=terminal)
    return render(request, "terminal_settings.html", {"form": form, "terminal": terminal})

def _terminal_json(terminal):
    return {
        "terminal_id": terminal.terminal_id,
        "protocol": terminal.protocol,
        "ip_address": str(terminal.ip_address),
        "dns_name": terminal.dns_name,
        "machine_os": terminal.machine_os,
        "port": terminal.port,
        "min_receiving_usd": str(terminal.min_receiving_usd),
        "max_receiving_usd": str(terminal.max_receiving_usd),
        "status": terminal.status,
        "created_at": terminal.created_at.isoformat(),
        "last_seen_at": terminal.last_seen_at.isoformat() if terminal.last_seen_at else None,
        "payment_processing": "test" if settings.ALLOW_TEST_FUNDS else "provider",
    }

@login_required
@csrf_protect
@require_http_methods(["GET", "POST"])
def terminal_api(request):
    if request.method == "GET":
        qs = Terminal.objects.filter(owner=request.user)
        return JsonResponse({"terminals": [_terminal_json(t) for t in qs]})
    try:
        data = json.loads(request.body or "{}")
        current = Terminal.objects.filter(owner=request.user).first()
        form = TerminalForm(data, instance=current)
        if not form.is_valid():
            return JsonResponse({"errors": form.errors.get_json_data()}, status=400)
        terminal = form.save(commit=False)
        terminal.owner = request.user
        terminal.full_clean()
        terminal.save()
        return JsonResponse({"terminal": _terminal_json(terminal)}, status=201 if current is None else 200)
    except json.JSONDecodeError:
        return JsonResponse({"error": "Request body must be valid JSON."}, status=400)
    except Exception as exc:
        # Avoid exposing internal exception details.
        return JsonResponse({"error": "Could not save terminal configuration."}, status=400)

@login_required
@csrf_protect
@require_http_methods(["GET", "PATCH"])
def terminal_detail_api(request, terminal_id):
    try:
        terminal = Terminal.objects.get(owner=request.user, terminal_id=terminal_id)
    except Terminal.DoesNotExist:
        return JsonResponse({"error": "Terminal not found."}, status=404)
    if request.method == "GET":
        return JsonResponse({"terminal": _terminal_json(terminal)})
    try:
        data = json.loads(request.body or "{}")
        # Partial update: merge supplied fields with existing values.
        current = {
            "terminal_id": terminal.terminal_id, "protocol": terminal.protocol,
            "ip_address": str(terminal.ip_address), "dns_name": terminal.dns_name,
            "machine_os": terminal.machine_os, "port": terminal.port,
            "min_receiving_usd": str(terminal.min_receiving_usd),
            "max_receiving_usd": str(terminal.max_receiving_usd),
        }
        current.update(data)
        form = TerminalForm(current, instance=terminal)
        if not form.is_valid():
            return JsonResponse({"errors": form.errors.get_json_data()}, status=400)
        obj = form.save(commit=False)
        obj.owner = request.user
        obj.full_clean()
        obj.save()
        return JsonResponse({"terminal": _terminal_json(obj)})
    except json.JSONDecodeError:
        return JsonResponse({"error": "Request body must be valid JSON."}, status=400)
    except Exception:
        return JsonResponse({"error": "Could not update terminal configuration."}, status=400)



def _authenticate_terminal_request(request):
    """Authenticate a machine-to-machine request with a terminal API key."""
    raw_key = request.headers.get("X-Terminal-Key", "")
    if not raw_key:
        return None
    prefix = raw_key[:10]
    terminal = Terminal.objects.select_related("owner").filter(api_key_prefix=prefix, status=Terminal.Status.ACTIVE).first()
    if terminal and terminal.authenticate_api_key(raw_key):
        terminal.last_seen_at = timezone.now()
        terminal.save(update_fields=["last_seen_at"])
        return terminal
    return None


@csrf_exempt
@require_http_methods(["POST"])
def transfer_api(request):
    """Terminal-to-terminal/internal transfer API with idempotency protection.

    Required headers:
      X-Terminal-Key: provisioned terminal credential
      Idempotency-Key: unique key for this logical transfer request
    """
    terminal = _authenticate_terminal_request(request)
    if terminal is None:
        return JsonResponse({"error": "Invalid or inactive terminal credential."}, status=401)
    idem = request.headers.get("Idempotency-Key", "").strip()
    if not idem or len(idem) > 128:
        return JsonResponse({"error": "A valid Idempotency-Key header is required."}, status=400)
    try:
        data = json.loads(request.body or "{}")
        recipient = User.objects.get(username__iexact=str(data.get("recipient", "")).strip())
        asset = str(data.get("asset", "")).upper()
        amount = minor_units(asset, data.get("amount"))
        memo = str(data.get("memo", ""))[:200]
        tx = post_transfer(terminal.owner, recipient, asset, amount, idempotency_key=idem, memo=memo)
        AuditLog.objects.create(user=terminal.owner, action="api_transfer", ip_address=request.META.get("REMOTE_ADDR"),
                                details=f"terminal={terminal.terminal_id} tx={tx.reference}")
        return JsonResponse({"status": "completed", "reference": str(tx.reference), "terminal_id": terminal.terminal_id})
    except User.DoesNotExist:
        return JsonResponse({"error": "Recipient not found."}, status=404)
    except (ValidationError, Wallet.DoesNotExist) as exc:
        return JsonResponse({"error": str(exc)}, status=400)
    except json.JSONDecodeError:
        return JsonResponse({"error": "Request body must be valid JSON."}, status=400)


@csrf_exempt
@require_http_methods(["POST"])
def provider_webhook(request, provider):
    """Generic signed webhook intake. It records provider events but does not
    credit funds until provider-specific verification/settlement logic is added.
    """
    secret = os.environ.get("PROVIDER_WEBHOOK_SECRET", "")
    if not secret:
        return JsonResponse({"error": "Webhook secret is not configured."}, status=503)
    signature = request.headers.get("X-Provider-Signature", "")
    expected = hmac.new(secret.encode(), request.body, hashlib.sha256).hexdigest()
    if not constant_time_compare(signature, expected):
        return JsonResponse({"error": "Invalid webhook signature."}, status=401)
    try:
        payload = json.loads(request.body or "{}")
    except json.JSONDecodeError:
        return JsonResponse({"error": "Invalid JSON payload."}, status=400)
    event_id = str(payload.get("id") or payload.get("event_id") or "")
    if not event_id:
        return JsonResponse({"error": "Provider event id is required."}, status=400)
    payload_hash = hashlib.sha256(request.body).hexdigest()
    event, created = ProviderEvent.objects.get_or_create(
        provider=provider, event_id=event_id,
        defaults={"event_type": str(payload.get("type", "")), "payload_hash": payload_hash},
    )
    if not created and event.payload_hash != payload_hash:
        return JsonResponse({"error": "Event id was previously used with different payload."}, status=409)
    return JsonResponse({"status": "accepted", "duplicate": not created})


# Pointer-based transfer workflow. The reference locates a transfer; login and
# recipient identity authorize access. Settlement uses the existing ledger.
import secrets
from datetime import timedelta
from django.db import IntegrityError
from .models import PayloadTransfer

@login_required
def create_payload_transfer(request):
    form = PayloadTransferForm(request.POST or None)
    if request.method == "POST" and form.is_valid():
        try:
            recipient = User.objects.get(username__iexact=form.cleaned_data["recipient_username"])
            if recipient.pk == request.user.pk:
                form.add_error("recipient_username", "Choose another user as the receiver.")
            else:
                amount = form.amount_minor()
                if amount <= 0:
                    raise ValidationError("Amount must be greater than zero.")
                wallet = Wallet.objects.get(user=request.user, asset=form.cleaned_data["asset"])
                if wallet.balance_minor < amount:
                    raise ValidationError("Insufficient balance for this transfer.")
                # A high-entropy pointer is generated server-side. No funds move
                # until the intended recipient accepts the payload.
                payload = PayloadTransfer.objects.create(
                    reference=secrets.token_urlsafe(32),
                    sender=request.user, recipient=recipient,
                    asset=form.cleaned_data["asset"], amount_minor=amount,
                    memo=form.cleaned_data.get("memo", ""),
                    expires_at=timezone.now() + timedelta(hours=24),
                )
                AuditLog.objects.create(user=request.user, action="payload_created",
                    details=f"ref={payload.reference[:12]} recipient_id={recipient.pk}")
                messages.success(request, "Transfer pointer created. Share the link only with the intended receiver.")
                return redirect("payload_detail", reference=payload.reference)
        except User.DoesNotExist:
            form.add_error("recipient_username", "Receiver not found.")
        except (ValidationError, Wallet.DoesNotExist) as e:
            form.add_error(None, e)

    return render(request, "payload_create.html", {"form": form})


@login_required
def payload_detail(request, reference):
    payload = PayloadTransfer.objects.filter(reference=reference).select_related(
        "sender", "recipient", "ledger_transaction"
    ).first()
    if payload is None:
        return render(request, "payload_detail.html", {"not_found": True}, status=404)
    if request.user.pk not in (payload.sender_id, payload.recipient_id):
        return render(request, "payload_detail.html", {"not_found": True}, status=404)
    if payload.status == PayloadTransfer.PENDING and timezone.now() >= payload.expires_at:
        payload.status = PayloadTransfer.EXPIRED
        payload.save(update_fields=["status"])
    return render(request, "payload_detail.html", {"payload": payload})


@login_required
def accept_payload_transfer(request, reference):
    if request.method != "POST":
        return HttpResponseNotAllowed(["POST"])
    with transaction.atomic():
        payload = PayloadTransfer.objects.select_for_update().filter(reference=reference).first()
        if payload is None or payload.recipient_id != request.user.pk:
            messages.error(request, "Transfer pointer not found.")
            return redirect("history")
        if payload.status != PayloadTransfer.PENDING:
            messages.error(request, "This transfer is no longer pending.")
            return redirect("payload_detail", reference=reference)
        if timezone.now() >= payload.expires_at:
            payload.status = PayloadTransfer.EXPIRED
            payload.save(update_fields=["status"])
            messages.error(request, "This transfer pointer has expired.")
            return redirect("payload_detail", reference=reference)
        try:
            tx = post_transfer(payload.sender, payload.recipient, payload.asset, payload.amount_minor)
        except (ValidationError, Wallet.DoesNotExist) as e:
            messages.error(request, f"Transfer could not be completed: {e}")
            return redirect("payload_detail", reference=reference)
        payload.status = PayloadTransfer.COMPLETED
        payload.completed_at = timezone.now()
        payload.ledger_transaction = tx
        payload.save(update_fields=["status", "completed_at", "ledger_transaction"])
        AuditLog.objects.create(user=request.user, action="payload_accepted",
            details=f"ref={payload.reference[:12]}")
    messages.success(request, f"Transfer completed. Ledger reference: {tx.reference}")
    return redirect("history")


@login_required
def cancel_payload_transfer(request, reference):
    if request.method != "POST":
        return HttpResponseNotAllowed(["POST"])
    payload = PayloadTransfer.objects.filter(reference=reference, sender=request.user,
                                              status=PayloadTransfer.PENDING).first()
    if payload is None:
        messages.error(request, "Pending transfer not found.")
    else:
        payload.status = PayloadTransfer.CANCELLED
        payload.save(update_fields=["status"])
        AuditLog.objects.create(user=request.user, action="payload_cancelled",
            details=f"ref={payload.reference[:12]}")
        messages.success(request, "Pending transfer cancelled.")
    return redirect("history")
