"""
Safaricom Daraja (M-Pesa) API integration.

Daraja delivers C2B transactions via a *confirmation callback*, so live
reconciliation has two parts:
  1. Register the validation/confirmation URLs (one-off, per shortcode).
  2. Receive confirmation callbacks → persist as MpesaStatementLine and
     auto-reconcile against the GL (same matching used by the CSV import).

`test_connection` verifies OAuth credentials; `register_c2b_urls` performs the
one-off URL registration. Credentials live on MpesaApiConfig per branch.
"""
from __future__ import annotations

import base64
from datetime import datetime

import requests

from .finance_extended import log_finance_audit
from .models import AccountHistory, MpesaStatementLine

BASE_URLS = {
    "sandbox": "https://sandbox.safaricom.co.ke",
    "production": "https://api.safaricom.co.ke",
}


def _base_url(config) -> str:
    return BASE_URLS.get(config.environment, BASE_URLS["sandbox"])


def get_access_token(config) -> str:
    """OAuth client-credentials token from Daraja."""
    if not config.consumer_key or not config.consumer_secret:
        raise ValueError("M-Pesa consumer key/secret not configured")
    url = f"{_base_url(config)}/oauth/v1/generate?grant_type=client_credentials"
    resp = requests.get(
        url, auth=(config.consumer_key, config.consumer_secret), timeout=30,
    )
    resp.raise_for_status()
    token = resp.json().get("access_token")
    if not token:
        raise ValueError("No access token returned by Daraja")
    return token


def test_connection(config) -> dict:
    token = get_access_token(config)
    return {"connected": True, "environment": config.environment, "token_prefix": token[:8]}


def register_c2b_urls(config, validation_url: str, confirmation_url: str) -> dict:
    token = get_access_token(config)
    url = f"{_base_url(config)}/mpesa/c2b/v1/registerurl"
    payload = {
        "ShortCode": config.short_code,
        "ResponseType": "Completed",
        "ConfirmationURL": confirmation_url,
        "ValidationURL": validation_url,
    }
    resp = requests.post(
        url, json=payload,
        headers={"Authorization": f"Bearer {token}"}, timeout=30,
    )
    resp.raise_for_status()
    return resp.json()


def _auto_reconcile(line: MpesaStatementLine):
    """Best-effort match against an unreconciled GL debit on the M-Pesa account."""
    account = line.mpesa_account
    if not account:
        return
    ref = (line.reference_code or "")[:20]
    history = None
    if ref:
        history = AccountHistory.objects.filter(
            account=account, reconciled=False,
        ).filter(description__icontains=ref).first()
    if not history:
        history = AccountHistory.objects.filter(
            account=account, debit=line.amount, reconciled=False,
        ).first()
    if history:
        history.reconciled = True
        history.save()
        line.matched = True
        line.matched_history = history
        line.save()


def ingest_c2b_confirmation(branch, config, payload: dict, staff=None) -> dict:
    """
    Persist a Daraja C2B confirmation callback as an MpesaStatementLine and
    auto-reconcile. Idempotent on the transaction id (TransID).
    """
    trans_id = (payload.get("TransID") or payload.get("reference_code") or "").strip()
    amount = float(payload.get("TransAmount") or payload.get("amount") or 0)
    name = " ".join(filter(None, [
        payload.get("FirstName", ""), payload.get("MiddleName", ""), payload.get("LastName", ""),
    ])).strip() or payload.get("payer_name", "")

    trans_time = payload.get("TransTime") or ""
    try:
        tx_date = datetime.strptime(trans_time, "%Y%m%d%H%M%S").date()
    except (ValueError, TypeError):
        tx_date = datetime.today().date()

    if trans_id and MpesaStatementLine.objects.filter(
        company_branch=branch, reference_code=trans_id,
    ).exists():
        return {"created": False, "reason": "duplicate", "reference_code": trans_id}

    line = MpesaStatementLine.objects.create(
        company_branch=branch,
        mpesa_account=config.mpesa_account if config else None,
        transaction_date=tx_date,
        reference_code=trans_id,
        payer_name=name,
        amount=amount,
    )
    _auto_reconcile(line)
    if staff:
        log_finance_audit(branch, staff, "mpesa_api", line.id, "ingested",
                          f"C2B {trans_id} {amount}")
    return {"created": True, "reference_code": trans_id, "matched": line.matched}
