from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, validator
from typing import List, Optional
import pandas as pd
import numpy as np
import joblib
import re
from rich.console import Console

console = Console()

MAX_TEXT_LEN = 10000  # максимальная длина текста для модели

# модели для валидации и отлова ошибок
class Item(BaseModel):
    name: str = ""
    price: float = 0.0

    @validator("price", pre=True)
    def fix_negative_price(cls, v):
        return max(0, float(v) if v is not None else 0)

class Transaction(BaseModel):
    transaction_id: str
    terminal_name: Optional[str] = "unknown"
    terminal_description: Optional[str] = "unknown"
    city: Optional[str] = "unknown"
    amount: float = 0.0
    items: Optional[List[Item]] = []

    @validator("amount", pre=True)
    def fix_negative_amount(cls, v):
        return max(0, float(v) if v is not None else 0)

class BatchRequest(BaseModel):
    transactions: List[Transaction]


app = FastAPI()


try:
    model = joblib.load("model/mcc_model.pkl")
    console.log("[green]Успешно загрузили модель[/green]")
    MODEL_READY = True
except Exception as e:
    console.log(f"[red]Ошибка загрузки модели: {e}[/red]")
    MODEL_READY = False

# препроцессим транзакции с кастомными типами, чтобы не валилось
def clean_text(text: str) -> str:
    text = re.sub(r"[^\w\s\.,'-]", " ", str(text))
    text = re.sub(r"\s+", " ", text).strip()
    return text[:MAX_TEXT_LEN]

def preprocess_transaction(tx: Transaction) -> pd.DataFrame:
    terminal_name = clean_text(tx.terminal_name)
    terminal_desc = clean_text(tx.terminal_description)
    city = clean_text(tx.city)

    item_names = [clean_text(i.name) for i in tx.items] if tx.items else []
    items_text = " ".join(item_names)

    combined_text = f"{terminal_name} {terminal_desc} {city} items {items_text}".lower()[:MAX_TEXT_LEN]

    df = pd.DataFrame([{
        "full_text": combined_text,
        "amount": tx.amount,
        "items_text": items_text,
        "items_count": len(tx.items),
        "items_total_price": sum(i.price for i in tx.items) if tx.items else 0,
        "items_max_price": max((i.price for i in tx.items), default=0),
        "items_min_price": min((i.price for i in tx.items), default=0)
    }])
    return df


@app.get("/health")
def health():
    if MODEL_READY:
        return {"status": "ok"}
    raise HTTPException(status_code=503, detail="Model not up")

@app.get("/model/info")
def info():
    return {"model_name": "mcc_classifier",
            "model_version": "1.0.67"}

@app.post("/predict")
def predict(tx: Transaction):
    if not MODEL_READY:
        raise HTTPException(status_code=503, detail="Model not up")

    try:
        df = preprocess_transaction(tx)
        pred = model.predict(df)[0]
        conf = model.predict_proba(df).max()
        console.log(f"[yellow]Предсказание для транзакции с id {tx.transaction_id}: {pred} (conf={conf:.4f})[/yellow]")
        return {
            "transaction_id": tx.transaction_id,
            "predicted_mcc": int(pred),
            "confidence": round(float(conf), 4)
        }
    except Exception as e:
        console.log(f"[red]Ошибка предсказания для id {tx.transaction_id}: {e}[/red]")
        raise HTTPException(status_code=400, detail=str(e))

@app.post("/predict/batch")
def predict_batch(batch: BatchRequest):
    if not MODEL_READY:
        raise HTTPException(status_code=503, detail="Model not up")
    results = []
    for tx in batch.transactions:
        try:
            df = preprocess_transaction(tx)
            pred = model.predict(df)[0]
            conf = model.predict_proba(df).max()
            results.append({
                "transaction_id": tx.transaction_id,
                "predicted_mcc": int(pred),
                "confidence": round(float(conf), 4)
            })
            console.log(f"[yellow]Предсказание для id {tx.transaction_id}: {pred} (conf={conf:.4f})[/yellow]")
        except Exception as e:
            console.log(f"[red]Ошибка предсказания для id {tx.transaction_id}: {e}[/red]")
            results.append({
                "transaction_id": tx.transaction_id,
                "predicted_mcc": None,
                "confidence": 0.0
            })
    return {"predictions": results}
