"""
GSTIN Verification Service — provider-agnostic with STUB fallback.

Purpose:
    1. Validate GSTIN structure + checksum (offline, no API call).
    2. Look up the business name, address, state, registration status,
       and trade name from the GST portal via a configured GSP provider.
    3. Cache results in `gstin_cache` collection to avoid repeated API
       quota-burns on the same GSTIN (24-hour TTL).
    4. Detect duplicate GSTIN usage within the same company before save.

Providers (auto-selected by env config; first non-empty wins):
    • MasterGST    — set MASTERGST_AUTH_TOKEN, MASTERGST_GSTIN (your registered)
    • Surepass     — set SUREPASS_TOKEN
    • ClearTax     — set CLEARTAX_API_TOKEN, CLEARTAX_GSP_USER, CLEARTAX_GSTIN
    • <STUB MODE>  — none configured → returns deterministic synthetic data
                     for known sample GSTINs and the structural validation
                     result for the rest. Lets the full UI flow be built
                     and demoed without burning API quota.

This module DOES NOT replace the structural validator in `registration.py`
(used during signup) — that one stays as-is. This module reuses it via
`validate_gstin()` and adds the live lookup on top.
"""
from __future__ import annotations

import logging
import os
from datetime import datetime, timedelta, timezone
from typing import Optional

import httpx
from bson import ObjectId
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel

from auth import get_current_user, require_admin
from registration import validate_gstin

logger = logging.getLogger("rge.gst_verify")

# -- Configuration ----------------------------------------------------------
MASTERGST_AUTH_TOKEN = os.environ.get("MASTERGST_AUTH_TOKEN", "").strip()
MASTERGST_GSTIN = os.environ.get("MASTERGST_GSTIN", "").strip()
SUREPASS_TOKEN = os.environ.get("SUREPASS_TOKEN", "").strip()
CLEARTAX_API_TOKEN = os.environ.get("CLEARTAX_API_TOKEN", "").strip()
CLEARTAX_GSP_USER = os.environ.get("CLEARTAX_GSP_USER", "").strip()
CLEARTAX_GSTIN = os.environ.get("CLEARTAX_GSTIN", "").strip()

CACHE_TTL_HOURS = int(os.environ.get("GSTIN_CACHE_TTL_HOURS", "24"))


def configured_provider() -> str:
    """Returns the name of the active provider, or 'stub' if none configured."""
    if MASTERGST_AUTH_TOKEN and MASTERGST_GSTIN:
        return "mastergst"
    if SUREPASS_TOKEN:
        return "surepass"
    if CLEARTAX_API_TOKEN and CLEARTAX_GSP_USER:
        return "cleartax"
    return "stub"


def _now() -> datetime:
    return datetime.now(timezone.utc)


def _now_iso() -> str:
    return _now().isoformat()


# -- Provider callers --------------------------------------------------------
async def _lookup_mastergst(gstin: str) -> dict:
    """MasterGST GSTIN Search API.
    Docs: https://api.mastergst.com/playground (GSTN APIs → search)
    """
    url = "https://commonapi.mastergst.com/commonapis/searchtp"
    headers = {
        "auth-token": MASTERGST_AUTH_TOKEN,
        "Content-Type": "application/json",
    }
    params = {"action": "TP", "gstin": gstin, "username": MASTERGST_GSTIN}
    async with httpx.AsyncClient(timeout=15) as client:
        r = await client.get(url, headers=headers, params=params)
        if r.status_code >= 400:
            raise HTTPException(502, f"MasterGST {r.status_code}: {r.text[:200]}")
        body = r.json()
    # Normalise response shape
    if not body or body.get("status_cd") not in ("1", 1, "Active"):
        return {"found": False, "raw": body}
    data = body.get("data") or body
    pradr = data.get("pradr", {})
    addr = pradr.get("addr", {}) if isinstance(pradr, dict) else {}
    return {
        "found": True,
        "legal_name": data.get("lgnm", ""),
        "trade_name": data.get("tradeNam") or data.get("trade_name", ""),
        "address_line": _join_address(addr),
        "city": addr.get("loc") or addr.get("city", ""),
        "pincode": addr.get("pncd", ""),
        "state": addr.get("stcd", ""),
        "status": data.get("sts") or data.get("status", "Active"),
        "registration_date": data.get("rgdt", ""),
        "constitution": data.get("ctb", ""),
        "taxpayer_type": data.get("dty", ""),
        "raw": body,
    }


async def _lookup_surepass(gstin: str) -> dict:
    """Surepass GSTIN verification (very common REST API).
    Docs: https://docs.surepass.io/v1.0/Gst-verification
    """
    url = "https://kyc-api.surepass.io/api/v1/corporate/gstin"
    headers = {
        "Authorization": f"Bearer {SUREPASS_TOKEN}",
        "Content-Type": "application/json",
    }
    async with httpx.AsyncClient(timeout=15) as client:
        r = await client.post(url, headers=headers, json={"id_number": gstin})
        if r.status_code >= 400:
            raise HTTPException(502, f"Surepass {r.status_code}: {r.text[:200]}")
        body = r.json()
    data = (body or {}).get("data") or {}
    if not data:
        return {"found": False, "raw": body}
    pradr = data.get("address", "") or ""
    return {
        "found": True,
        "legal_name": data.get("business_name", ""),
        "trade_name": data.get("legal_name", "") or data.get("trade_name", ""),
        "address_line": pradr,
        "city": data.get("city", ""),
        "pincode": data.get("pincode", ""),
        "state": data.get("state", ""),
        "status": data.get("gstin_status") or data.get("status", "Active"),
        "registration_date": data.get("date_of_registration", ""),
        "constitution": data.get("constitution_of_business", ""),
        "taxpayer_type": data.get("taxpayer_type", ""),
        "raw": body,
    }


async def _lookup_cleartax(gstin: str) -> dict:
    """ClearTax public-search endpoint (compliance API).
    Note: requires GSP onboarding. Sandbox-safe wrapper.
    """
    url = "https://gst-api.cleartax.in/v0.1/gstin/" + gstin
    headers = {
        "Authorization": f"Bearer {CLEARTAX_API_TOKEN}",
        "gsp-user": CLEARTAX_GSP_USER,
        "Content-Type": "application/json",
    }
    async with httpx.AsyncClient(timeout=15) as client:
        r = await client.get(url, headers=headers)
        if r.status_code >= 400:
            raise HTTPException(502, f"ClearTax {r.status_code}: {r.text[:200]}")
        body = r.json()
    data = body.get("response") or body.get("data") or body
    if not data:
        return {"found": False, "raw": body}
    pradr = data.get("pradr", {}).get("addr", {}) if isinstance(data.get("pradr"), dict) else {}
    return {
        "found": True,
        "legal_name": data.get("lgnm", ""),
        "trade_name": data.get("tradeNam", ""),
        "address_line": _join_address(pradr),
        "city": pradr.get("loc", ""),
        "pincode": pradr.get("pncd", ""),
        "state": pradr.get("stcd", ""),
        "status": data.get("sts", "Active"),
        "registration_date": data.get("rgdt", ""),
        "constitution": data.get("ctb", ""),
        "taxpayer_type": data.get("dty", ""),
        "raw": body,
    }


def _join_address(addr: dict) -> str:
    if not isinstance(addr, dict):
        return ""
    parts = [
        addr.get("bno"), addr.get("bnm"), addr.get("flno"),
        addr.get("st"), addr.get("loc"), addr.get("dst"),
        addr.get("stcd"), addr.get("pncd"),
    ]
    return ", ".join([p for p in parts if p])


# Known sample GSTINs for STUB mode — Hindi/English business names so devs
# can demo the UI without real provider quota.
STUB_DATA = {
    "30ARLPR3709H1ZT": {
        "legal_name": "REGAL MARKETING",
        "trade_name": "REGAL MARKETING (Goa)",
        "address_line": "MARGAO, SOUTH GOA, GOA - 403601",
        "city": "Margao", "pincode": "403601", "state": "Goa",
        "constitution": "Proprietorship", "taxpayer_type": "Regular",
        "registration_date": "2017-07-01",
    },
    "27AAAAA0000A1Z2": {
        "legal_name": "DEMO ENTERPRISES PVT LTD",
        "trade_name": "Demo Enterprises",
        "address_line": "Andheri East, Mumbai, Maharashtra - 400069",
        "city": "Mumbai", "pincode": "400069", "state": "Maharashtra",
        "constitution": "Private Limited Company", "taxpayer_type": "Regular",
        "registration_date": "2018-04-15",
    },
}


async def _lookup_stub(gstin: str) -> dict:
    """Deterministic stub — known sample GSTINs return realistic data,
    others get state-only info from structural validation. NEVER call this
    in production (use real provider)."""
    v = validate_gstin(gstin)
    if not v["valid"]:
        return {"found": False, "raw": {"reason": v["reason"]}}
    if gstin in STUB_DATA:
        d = dict(STUB_DATA[gstin])
        d["found"] = True
        d["status"] = "Active"
        d["raw"] = {"stub": True}
        return d
    return {
        "found": True,
        "legal_name": "(Stub) Business name not available",
        "trade_name": "",
        "address_line": "",
        "city": "",
        "pincode": "",
        "state": v["state_name"],
        "status": "Unknown (stub mode)",
        "registration_date": "",
        "constitution": "",
        "taxpayer_type": "",
        "raw": {"stub": True, "structural": v},
    }


PROVIDER_HANDLERS = {
    "mastergst": _lookup_mastergst,
    "surepass": _lookup_surepass,
    "cleartax": _lookup_cleartax,
    "stub": _lookup_stub,
}


# -- Cache + main verify entrypoint ------------------------------------------
async def lookup_gstin(db, gstin: str, force_refresh: bool = False) -> dict:
    """Returns the normalised verification dict. Reads/writes cache."""
    g = (gstin or "").strip().upper()
    # Structural pre-check — cheap reject before hitting the API
    structural = validate_gstin(g)
    if not structural["valid"]:
        return {
            "ok": False, "valid": False, "found": False,
            "reason": structural["reason"], "gstin": g,
            "provider": configured_provider(),
        }

    if not force_refresh:
        cached = await db.gstin_cache.find_one({"_id": g})
        if cached:
            try:
                exp = datetime.fromisoformat(cached.get("expires_at", ""))
                if exp.tzinfo is None:
                    exp = exp.replace(tzinfo=timezone.utc)
                if exp > _now():
                    cached.pop("_id", None)
                    cached["gstin"] = g
                    cached["valid"] = True
                    cached["ok"] = True
                    cached["from_cache"] = True
                    return cached
            except (ValueError, TypeError):
                pass  # bad cache entry → re-fetch

    provider = configured_provider()
    handler = PROVIDER_HANDLERS[provider]
    try:
        info = await handler(g)
    except HTTPException:
        raise
    except Exception as ex:
        logger.exception("GST lookup error via %s", provider)
        raise HTTPException(502, f"Provider {provider} failed: {ex}")

    result = {
        "ok": True,
        "valid": True,
        "gstin": g,
        "found": info.get("found", False),
        "provider": provider,
        "state_code": structural["state_code"],
        "state_name_from_code": structural["state_name"],
        "pan": structural["pan"],
        "legal_name": info.get("legal_name", ""),
        "trade_name": info.get("trade_name", ""),
        "address_line": info.get("address_line", ""),
        "city": info.get("city", ""),
        "pincode": info.get("pincode", ""),
        "state": info.get("state", structural["state_name"]),
        "status": info.get("status", ""),
        "registration_date": info.get("registration_date", ""),
        "constitution": info.get("constitution", ""),
        "taxpayer_type": info.get("taxpayer_type", ""),
        "looked_up_at": _now_iso(),
        "expires_at": (_now() + timedelta(hours=CACHE_TTL_HOURS)).isoformat(),
    }

    # Persist cache — best effort, never blocks the user
    try:
        await db.gstin_cache.update_one(
            {"_id": g},
            {"$set": {**result, "_id": g}},
            upsert=True,
        )
    except Exception as ex:
        logger.warning("gstin_cache write failed: %s", ex)
    return result


# -- Router -----------------------------------------------------------------
router = APIRouter(prefix="/api/gst", tags=["gst-verify"])


class VerifyIn(BaseModel):
    gstin: str
    force_refresh: Optional[bool] = False
    company_id: Optional[str] = None  # for duplicate-detection scope


@router.post("/verify")
async def verify_gstin(payload: VerifyIn, request: Request, user=Depends(get_current_user)):
    """Live GSTIN lookup. Authenticated. Caches result for 24 h.
    Optionally returns `duplicate_party` if the GSTIN is already used by
    another party within `company_id`.
    """
    db = request.app.state.db
    g = (payload.gstin or "").strip().upper()
    result = await lookup_gstin(db, g, force_refresh=bool(payload.force_refresh))

    if payload.company_id and result.get("valid"):
        try:
            dup = await db.parties.find_one({
                "company_id": payload.company_id,
                "gstin": g,
            }, {"name": 1, "type": 1})
            if dup:
                result["duplicate_party"] = {
                    "id": str(dup["_id"]),
                    "name": dup.get("name", ""),
                    "type": dup.get("type", "customer"),
                }
        except Exception:
            pass

    # Audit
    try:
        await db.audit_log.insert_one({
            "ts": _now_iso(),
            "user_id": user.get("id"),
            "email": user.get("email"),
            "action": "gst.verify",
            "outcome": "ok" if result.get("ok") else "failed",
            "detail": {
                "gstin": g,
                "provider": result.get("provider"),
                "found": result.get("found"),
                "from_cache": result.get("from_cache", False),
            },
        })
    except Exception:
        pass
    return result


@router.get("/provider-status")
async def provider_status(user=Depends(get_current_user)):
    """Tells the UI which provider is active. Used to show banners
    ('Stub mode — live verification will return synthetic data')."""
    p = configured_provider()
    return {
        "provider": p,
        "is_live": p != "stub",
        "cache_ttl_hours": CACHE_TTL_HOURS,
    }


@router.delete("/cache/{gstin}")
async def admin_clear_cache(gstin: str, request: Request, user=Depends(require_admin)):
    """Admin can wipe a stale cached GSTIN (e.g. business name changed)."""
    db = request.app.state.db
    g = (gstin or "").strip().upper()
    res = await db.gstin_cache.delete_one({"_id": g})
    return {"ok": True, "removed": res.deleted_count}
