"""
TrueDoor Backend - Main Flask Application.

Property valuation service integrating:
- MCP (Model Context Protocol) for property searches
- ATTOM Data Solutions for comparable properties
- Google Gemini for LLM-based fallback estimation
"""

import asyncio
import logging
from typing import Any, Dict

import pandas as pd
import google.generativeai as genai
from flask import Flask, request, jsonify
from flask_cors import CORS

import config
from utils import estimate_from_comps, estimate_property_value
from services import lookup_property, get_attom_estimate



logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)



gemini_client = None

if not config.GEMINI_API_KEY:
    logger.warning("GEMINI_API_KEY not set. Gemini estimation disabled.")
else:
    genai.configure(api_key=config.GEMINI_API_KEY)
    
    def pick_model():
        """Pick best available Gemini model from preferred list."""
        try:
            models = list(genai.list_models())
            names = {
                m.name.split("/")[-1] for m in models
                if "generateContent" in getattr(m, "supported_generation_methods", [])
            }
            for name in config.GEMINI_PREFERRED_MODELS:
                if name in names:
                    return name
            return next(iter(names)) if names else config.GEMINI_PREFERRED_MODELS[0]
        except Exception:
            return config.GEMINI_PREFERRED_MODELS[0]
    
    try:
        model_name = pick_model()
        gemini_client = genai.GenerativeModel(model_name)
        logger.info(f"Using Gemini model: {model_name}")
    except Exception:
        gemini_client = None
        logger.exception("Failed to initialize Gemini model")

# ────────────────────────────────────────────────────────────────
# FLASK APP SETUP
# ────────────────────────────────────────────────────────────────

app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)

# ────────────────────────────────────────────────────────────────
# ROUTES
# ────────────────────────────────────────────────────────────────

@app.route("/", methods=["GET"])
def health():
    """Health check endpoint."""
    return jsonify({"status": "ok"})


@app.route("/address", methods=["POST"])
def address_lookup():
   
    payload = request.get_json(silent=True)
    if not payload or "address" not in payload:
        return jsonify({"error": "Missing 'address' in JSON body"}), 400
    
    address = payload.get("address")
    
    # Look up property details via MCP
    try:
        df = asyncio.run(lookup_property(address))
    except Exception as e:
        logger.exception("Lookup failed")
        return jsonify({"error": "lookup failed", "detail": str(e)}), 500
    
    if df.empty:
        return jsonify({"address": address, "results": [], "estimate": {"error": "no data"}})
    
    df = df.where(pd.notnull(df), None)
    records = df.to_dict(orient="records")
    estimate_obj: Dict[str, Any] = None
    
    if config.ATTOM_API_KEY:
        try:
            subject = records[0]
            attom_result = get_attom_estimate(
                subject.get("street") or "",
                subject.get("city") or "",
                subject.get("state") or "",
                subject.get("zip_code") or "",
                subject_sqft=subject.get("sqft")
            )
            
            subject2 = attom_result.get("subject")
            comps = attom_result.get("comps", [])
            
            logger.info("ATTOM parsed subject sqft=%s", subject2.get("sqft") if subject2 else None)
            logger.info("ATTOM parsed comps count=%d", len(comps))
            
            subject_sqft_attom = subject2.get("sqft") if subject2 else None
            subject_sqft_mcp = subject.get("sqft")
            subject_sqft = subject_sqft_mcp or subject_sqft_attom
            
            if subject_sqft_attom and subject_sqft_mcp and \
               abs(subject_sqft_attom - subject_sqft_mcp) / max(subject_sqft_attom, subject_sqft_mcp) > 0.35:
                subject_sqft = subject_sqft_mcp
            
            logger.info(
                "Subject sqft chosen for estimate=%s (mcp=%s, attom=%s)",
                subject_sqft, subject_sqft_mcp, subject_sqft_attom
            )
            
            numeric = estimate_from_comps(subject_sqft or 0, comps)
            logger.info("ATTOM numeric result: %s", numeric)
            
            if numeric["estimated_price"] > 0:
                conf = "High" if numeric["n_comps"] >= 10 else \
                       "Medium" if numeric["n_comps"] >= 5 else "Low"
                estimate_obj = {
                    "estimated_price": numeric["estimated_price"],
                    "note": f"Based on {numeric['n_comps']} comps @ ${numeric['ppsf_avg']:,.0f}/sqft",
                    "confidence": conf,
                    "source": "attom_comps_v2",
                    "ppsf_avg": numeric["ppsf_avg"],
                    "n_comps": numeric["n_comps"],
                    "display": f"${numeric['estimated_price']:,.0f}\nBased on comps\n{conf}"
                }
        except Exception:
            logger.exception("ATTOM comps fetch failed")
    
    # LLM fallback
    if not estimate_obj:
        try:
            est = estimate_property_value(records[0], gemini_client)
            if isinstance(est, dict):
                lp = records[0].get("list_price")
                note = est.get("note") or ""
                conf = est.get("confidence") or "Low"
                if lp and isinstance(est.get("estimated_price"), int) and est["estimated_price"] == int(lp):
                    note = "Matches list price."
                    conf = "Low" if conf == "High" else conf
                estimate_obj = {
                    **est, "note": note, "confidence": conf,
                    "source": "llm-fallback",
                    "display": f"${est.get('estimated_price', 0):,.0f}\n{note}\n{conf}"
                }
            else:
                estimate_obj = {"error": str(est)}
        except Exception:
            logger.exception("LLM estimate failed")
    
    # Final numeric fallback
    if not estimate_obj:
        lp = records[0].get("list_price")
        sqft = records[0].get("sqft")
        if lp and sqft:
            ppsf = lp / sqft
            est_price = int(round(ppsf * sqft / 1000.0) * 1000)
            estimate_obj = {
                "estimated_price": est_price,
                "note": "Fallback ppsf",
                "confidence": "Low",
                "source": "local_ppsf"
            }
        elif lp:
            estimate_obj = {
                "estimated_price": int(lp),
                "note": "List price fallback",
                "confidence": "Low",
                "source": "list_price"
            }
        else:
            estimate_obj = {"error": "No estimate available"}
    
    return jsonify({"address": address, "results": records, "estimate": estimate_obj})


@app.route('/suggest', methods=['GET'])
def suggest():
    """Return address suggestions for a partial query.

    Query param: q (string)
    Returns JSON: { suggestions: [ { label, street, city, state, zip_code } ] }
    """
    q = request.args.get('q', '').strip()
    if not q or len(q) < 2:
        return jsonify({"suggestions": []})

    try:
        # reuse existing MCP lookup which supports a small limit
        df = asyncio.run(lookup_property(q))
    except Exception:
        logger.exception("Suggest lookup failed")
        return jsonify({"suggestions": []})

    if df.empty:
        return jsonify({"suggestions": []})

    df = df.where(pd.notnull(df), None)
    records = df.to_dict(orient="records")
    suggestions = []
    for r in records:
        label = r.get('street') or ''
        city = r.get('city') or ''
        state = r.get('state') or ''
        zipc = r.get('zip_code') or ''
        full = f"{label} {city} {state} {zipc}".strip()
        suggestions.append({
            "label": full,
            "street": label,
            "city": city,
            "state": state,
            "zip_code": zipc,
        })

    return jsonify({"suggestions": suggestions})



if __name__ == "__main__":
    print(f"Starting Flask server on {config.FLASK_HOST}:{config.FLASK_PORT}...")
    app.run(
        host=config.FLASK_HOST,
        port=config.FLASK_PORT,
        debug=config.FLASK_DEBUG,
        use_reloader=False
    )
