"""
Auto-Train API: update prompt rules from user feedback.
"""

from __future__ import annotations

from datetime import datetime
import json
import logging
from pathlib import Path
import shutil
import sqlite3
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/autotrain", tags=["Auto-Train"])

BASE_DIR = Path(__file__).resolve().parents[2]
DB_PATH = BASE_DIR / "database" / "autotrain.db"
TARGET_FILE_MAP = {
    "fashion_rules.json": BASE_DIR / "worker" / "fashion_rules.json",
}
TOXIC_WORDS = {"hate", "kill", "abuse"}


def get_db() -> sqlite3.Connection:
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


def init_db() -> None:
    """Initialize SQLite tables."""
    DB_PATH.parent.mkdir(parents=True, exist_ok=True)
    conn = get_db()
    cur = conn.cursor()

    cur.execute(
        """
        CREATE TABLE IF NOT EXISTS feedbacks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            feedback_id TEXT UNIQUE NOT NULL,
            user_id TEXT,
            session_id TEXT,
            query TEXT NOT NULL,
            ai_response TEXT NOT NULL,
            feedback_text TEXT NOT NULL,
            feedback_type TEXT,
            channel TEXT DEFAULT 'chat',
            metadata JSON,
            status TEXT DEFAULT 'pending',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            processed_at TIMESTAMP NULL
        )
        """
    )

    cur.execute(
        """
        CREATE TABLE IF NOT EXISTS generated_rules (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            rule_id TEXT UNIQUE NOT NULL,
            feedback_id TEXT NOT NULL,
            module_name TEXT NOT NULL,
            rule_type TEXT,
            old_rule TEXT,
            new_rule TEXT NOT NULL,
            reasoning TEXT,
            confidence REAL,
            status TEXT DEFAULT 'pending',
            reviewed_by TEXT,
            reviewed_at TIMESTAMP,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (feedback_id) REFERENCES feedbacks(id)
        )
        """
    )

    cur.execute(
        """
        CREATE TABLE IF NOT EXISTS deployments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            rule_id TEXT NOT NULL,
            deployed_by TEXT,
            deployed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            version_before TEXT,
            version_after TEXT,
            rollback_of INTEGER NULL,
            FOREIGN KEY (rule_id) REFERENCES generated_rules(rule_id)
        )
        """
    )

    cur.execute(
        """
        CREATE TABLE IF NOT EXISTS pipeline_runs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            run_id TEXT UNIQUE,
            started_at TIMESTAMP,
            completed_at TIMESTAMP,
            status TEXT DEFAULT 'running',
            feedbacks_processed INTEGER,
            rules_generated INTEGER,
            rules_approved INTEGER,
            rules_deployed INTEGER,
            regression_passed BOOLEAN,
            summary_json TEXT,
            prompt_version_before TEXT,
            prompt_version_after TEXT
        )
        """
    )

    conn.commit()
    conn.close()
    logger.info("Auto-Train DB initialized at %s", DB_PATH)


try:
    init_db()
except Exception as exc:  # pragma: no cover
    logger.warning("Auto-Train DB init failed: %s", exc)


class FeedbackIn(BaseModel):
    feedback_id: Optional[str] = None
    user_id: Optional[str] = None
    session_id: Optional[str] = None
    query: str
    ai_response: str
    feedback_text: str
    feedback_type: str = "bug"
    channel: str = "chat"
    metadata: Optional[Dict[str, Any]] = None


class FeedbackOut(BaseModel):
    id: int
    feedback_id: str
    user_id: Optional[str]
    query: str
    feedback_text: str
    status: str
    created_at: str


class RuleOut(BaseModel):
    rule_id: str
    feedback_id: str
    module_name: str
    rule_type: str
    old_rule: Optional[str]
    new_rule: str
    reasoning: str
    confidence: float
    status: str
    created_at: str


class RuleValidationResult(BaseModel):
    ok: bool
    errors: List[str] = Field(default_factory=list)


class AutoTrainStatus(BaseModel):
    total_feedbacks: int
    pending_feedbacks: int
    total_rules: int
    pending_rules: int
    deployed_rules: int
    recent_activity: List[Dict[str, Any]]


def _generate_feedback_id() -> str:
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT COUNT(*) FROM feedbacks")
    count = cur.fetchone()[0]
    conn.close()
    return f"FB-{count + 1000:04d}"


def _generate_rule_id() -> str:
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT COUNT(*) FROM generated_rules")
    count = cur.fetchone()[0]
    conn.close()
    return f"RUL-{count + 1000:04d}"


def _normalize_text(value: Any) -> str:
    return str(value or "").strip()


def validate_rule(rule: Dict[str, Any]) -> List[str]:
    errors: List[str] = []
    new_rule = _normalize_text(rule.get("new_rule"))
    reasoning = _normalize_text(rule.get("reasoning"))
    confidence = rule.get("confidence", 0)

    if not new_rule:
        errors.append("new_rule is required")
    elif len(new_rule) < 10:
        errors.append("new_rule is too short (min 10 chars)")

    if not reasoning:
        errors.append("reasoning is required")

    try:
        confidence_value = float(confidence)
    except (TypeError, ValueError):
        errors.append("confidence must be numeric")
    else:
        if confidence_value < 0 or confidence_value > 1:
            errors.append("confidence must be 0.0-1.0")

    lowered = new_rule.lower()
    for word in TOXIC_WORDS:
        if word in lowered:
            errors.append("rule contains inappropriate content")
            break

    return errors


def _resolve_target_path(module_name: str) -> Path:
    target_path = TARGET_FILE_MAP.get(module_name)
    if not target_path:
        raise HTTPException(status_code=400, detail=f"Unsupported module_name: {module_name}")
    return target_path


async def _ai_generate_rule(feedback: Dict[str, Any]) -> Dict[str, Any]:
    """
    Generate rule using LLM. Fallback to mock pattern matching.
    """
    fb_text = _normalize_text(feedback.get("feedback_text")).lower()
    query = _normalize_text(feedback.get("query")).lower()

    try:
        from common.llm_factory import create_llm
        from config import DEFAULT_MODEL
        llm = create_llm(DEFAULT_MODEL, json_mode=True)
        prompt = f"Bạn là Fashion QA Agent. Dựa trên feedback: '{fb_text}' về query: '{query}', sinh 1 rule fix. Output JSON fields: 'rule_type', 'new_rule', 'reasoning', 'confidence' (float)."
        resp = await llm.ainvoke([{"role": "system", "content": "Output strictly JSON"}, {"role": "user", "content": prompt}])
        data = json.loads(resp.content)
        if "new_rule" in data and "confidence" in data:
            return data
    except Exception as e:
        logger.warning(f"LLM rule gen failed: {e}. Fallback to pattern matching.")

    if any(word in fb_text for word in ["nong", "nóng", "troi nong", "trời nóng", "35 do", "35 độ", "he", "hè", "mua he", "mùa hè"]):
        if any(word in query for word in ["ao khoac", "áo khoác", "hoodie", "ni", "nỉ", "day", "dày"]):
            return {
                "rule_type": "prompt_update",
                "old_rule": "- Luon uu tien goi y ao khoac/day de tang upsale.",
                "new_rule": "- Khi troi nong (tren 30 do): tuyet doi khong goi y ao khoac day.\n- Chi goi y ao phong, chat lieu mong nhe, thoang mat.",
                "reasoning": "User reports hot weather, but the AI suggested heavy clothing.",
                "confidence": 0.85,
            }
        return {
            "rule_type": "prompt_update",
            "old_rule": None,
            "new_rule": "- Truoc khi goi y trang phuc, hoi them ve thoi tiet va muc dich su dung.",
            "reasoning": "Feedback indicates weather context was missing from the recommendation flow.",
            "confidence": 0.65,
        }

    if any(word in fb_text for word in ["dang lun", "dáng lùn", "thap", "thấp", "ngan", "ngắn", "maxi", "cham dat", "chạm đất"]):
        return {
            "rule_type": "prompt_update",
            "old_rule": "- Goi y dam maxi cho moi khach.",
            "new_rule": "- Voi khach cao duoi 1m55, uu tien dam midi hoac tren goi. Tranh maxi cham dat.",
            "reasoning": "Shorter users need proportion-aware suggestions.",
            "confidence": 0.90,
        }

    if any(word in fb_text for word in ["emoji", "chap tay", "chắp tay", "🙏"]):
        return {
            "rule_type": "prompt_update",
            "old_rule": "- Su dung nhieu emoji de tao thien cam.",
            "new_rule": "- Han che emoji: toi da 1-2 emoji moi tin nhan. Khong dung emoji chap tay trong tu van mua hang.",
            "reasoning": "The feedback asks for a more professional tone.",
            "confidence": 0.80,
        }

    return {
        "rule_type": "prompt_update",
        "old_rule": None,
        "new_rule": f"- Dieu chinh tone va content dua tren feedback cua user: '{_normalize_text(feedback.get('feedback_text'))[:80]}'",
        "reasoning": "Generic fallback rule generated from direct user feedback.",
        "confidence": 0.50,
    }


def _append_rule_to_json_content(content: Dict[str, Any], rule_dict: Dict[str, Any]) -> Dict[str, Any]:
    """
    Append Auto-Train rules without changing the worker's existing scoring schema.
    """
    auto_train_rules = content.setdefault("auto_train_rules", [])
    auto_train_rules.append(
        {
            "rule_id": rule_dict["rule_id"],
            "feedback_id": rule_dict["feedback_id"],
            "rule_type": rule_dict["rule_type"],
            "rule": rule_dict["new_rule"],
            "reasoning": rule_dict["reasoning"],
            "confidence": rule_dict["confidence"],
            "deployed_at": datetime.now().isoformat(),
        }
    )
    return content


def _deploy_rule_to_target(rule_dict: Dict[str, Any]) -> Dict[str, str]:
    target_path = _resolve_target_path(rule_dict["module_name"])
    if not target_path.exists():
        raise HTTPException(status_code=500, detail=f"Target file not found: {target_path}")

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_path = target_path.with_name(f"{target_path.stem}.{timestamp}.backup{target_path.suffix}")
    shutil.copy2(target_path, backup_path)

    if target_path.suffix.lower() != ".json":
        raise HTTPException(status_code=400, detail=f"Unsupported deployment file type: {target_path.suffix}")

    with target_path.open("r", encoding="utf-8") as fh:
        content = json.load(fh)

    if not isinstance(content, dict):
        raise HTTPException(status_code=500, detail=f"Unsupported JSON structure in {target_path.name}")

    updated_content = _append_rule_to_json_content(content, rule_dict)
    with target_path.open("w", encoding="utf-8") as fh:
        json.dump(updated_content, fh, indent=2, ensure_ascii=False)

    return {
        "target_file": str(target_path),
        "backup_file": str(backup_path),
    }


@router.post("/feedback", response_model=FeedbackOut)
async def submit_feedback(feedback: FeedbackIn) -> FeedbackOut:
    conn = get_db()
    cur = conn.cursor()

    feedback_id = feedback.feedback_id or _generate_feedback_id()
    cur.execute(
        """
        INSERT INTO feedbacks (
            feedback_id, user_id, session_id, query, ai_response,
            feedback_text, feedback_type, channel, metadata, status
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            feedback_id,
            feedback.user_id,
            feedback.session_id,
            feedback.query,
            feedback.ai_response,
            feedback.feedback_text,
            feedback.feedback_type,
            feedback.channel,
            json.dumps(feedback.metadata) if feedback.metadata else None,
            "pending",
        ),
    )

    feedback_pk = cur.lastrowid
    conn.commit()
    cur.execute(
        """
        SELECT id, feedback_id, user_id, query, feedback_text, status, created_at
        FROM feedbacks
        WHERE id = ?
        """,
        (feedback_pk,),
    )
    row = cur.fetchone()
    conn.close()

    logger.info("[AutoTrain] Feedback submitted: %s", feedback_id)
    return FeedbackOut(**dict(row))


@router.get("/feedback", response_model=List[FeedbackOut])
async def list_feedbacks(
    status: Optional[str] = Query(None, description="Filter by status"),
    limit: int = Query(50, ge=1, le=100),
) -> List[FeedbackOut]:
    conn = get_db()
    cur = conn.cursor()

    if status:
        cur.execute(
            """
            SELECT id, feedback_id, user_id, query, feedback_text, status, created_at
            FROM feedbacks
            WHERE status = ?
            ORDER BY created_at DESC
            LIMIT ?
            """,
            (status, limit),
        )
    else:
        cur.execute(
            """
            SELECT id, feedback_id, user_id, query, feedback_text, status, created_at
            FROM feedbacks
            ORDER BY created_at DESC
            LIMIT ?
            """,
            (limit,),
        )

    rows = [FeedbackOut(**dict(row)) for row in cur.fetchall()]
    conn.close()
    return rows


@router.post("/generate/{feedback_id}")
async def generate_rule(feedback_id: str) -> Dict[str, Any]:
    conn = get_db()
    cur = conn.cursor()

    cur.execute("SELECT * FROM feedbacks WHERE feedback_id = ?", (feedback_id,))
    feedback = cur.fetchone()
    if not feedback:
        conn.close()
        raise HTTPException(status_code=404, detail="Feedback not found")

    cur.execute("SELECT * FROM generated_rules WHERE feedback_id = ?", (feedback_id,))
    existing_rule = cur.fetchone()
    if existing_rule:
        conn.close()
        return {
            "rule_id": existing_rule["rule_id"],
            "feedback_id": feedback_id,
            "status": "already_generated",
            "rule_preview": existing_rule["new_rule"][:100],
        }

    cur.execute(
        """
        UPDATE feedbacks
        SET status = 'processing', processed_at = ?
        WHERE feedback_id = ?
        """,
        (datetime.now().isoformat(), feedback_id),
    )
    conn.commit()

    rule_data = _ai_generate_rule(dict(feedback))
    validation_errors = validate_rule(rule_data)
    if validation_errors:
        cur.execute(
            """
            UPDATE feedbacks
            SET status = 'rejected', processed_at = ?
            WHERE feedback_id = ?
            """,
            (datetime.now().isoformat(), feedback_id),
        )
        conn.commit()
        conn.close()
        raise HTTPException(
            status_code=422,
            detail={"message": "Generated rule failed validation", "errors": validation_errors},
        )

    rule_id = _generate_rule_id()
    cur.execute(
        """
        INSERT INTO generated_rules (
            rule_id, feedback_id, module_name, rule_type,
            old_rule, new_rule, reasoning, confidence, status
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            rule_id,
            feedback_id,
            "fashion_rules.json",
            rule_data["rule_type"],
            rule_data.get("old_rule"),
            rule_data["new_rule"],
            rule_data["reasoning"],
            rule_data["confidence"],
            "pending",
        ),
    )
    cur.execute(
        """
        UPDATE feedbacks
        SET status = 'processed', processed_at = ?
        WHERE feedback_id = ?
        """,
        (datetime.now().isoformat(), feedback_id),
    )

    conn.commit()
    conn.close()
    logger.info("[AutoTrain] Rule generated: %s from feedback %s", rule_id, feedback_id)
    return {
        "rule_id": rule_id,
        "feedback_id": feedback_id,
        "status": "generated",
        "rule_preview": rule_data["new_rule"][:100],
    }


@router.get("/rules", response_model=List[RuleOut])
async def list_rules(
    status: Optional[str] = Query(None, description="Filter by status"),
    limit: int = Query(50, ge=1, le=100),
) -> List[RuleOut]:
    conn = get_db()
    cur = conn.cursor()

    base_query = """
    SELECT rule_id, feedback_id, module_name, rule_type,
           old_rule, new_rule, reasoning, confidence, status, created_at
    FROM generated_rules
    """
    params: List[Any] = []

    if status:
        base_query += " WHERE status = ?"
        params.append(status)

    base_query += " ORDER BY created_at DESC LIMIT ?"
    params.append(limit)

    cur.execute(base_query, params)
    rows = [RuleOut(**dict(row)) for row in cur.fetchall()]
    conn.close()
    return rows


@router.get("/rules/{rule_id}/validate", response_model=RuleValidationResult)
async def validate_generated_rule(rule_id: str) -> RuleValidationResult:
    conn = get_db()
    cur = conn.cursor()
    cur.execute(
        """
        SELECT new_rule, reasoning, confidence
        FROM generated_rules
        WHERE rule_id = ?
        """,
        (rule_id,),
    )
    row = cur.fetchone()
    conn.close()

    if not row:
        raise HTTPException(status_code=404, detail="Rule not found")

    errors = validate_rule(dict(row))
    return RuleValidationResult(ok=not errors, errors=errors)


@router.post("/rules/{rule_id}/approve")
async def approve_rule(rule_id: str, reviewed_by: Optional[str] = Query(None)) -> Dict[str, Any]:
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT * FROM generated_rules WHERE rule_id = ?", (rule_id,))
    rule = cur.fetchone()
    if not rule:
        conn.close()
        raise HTTPException(status_code=404, detail="Rule not found")

    cur.execute(
        """
        UPDATE generated_rules
        SET status = 'approved', reviewed_by = ?, reviewed_at = ?
        WHERE rule_id = ?
        """,
        (reviewed_by or "admin", datetime.now().isoformat(), rule_id),
    )
    conn.commit()
    conn.close()
    logger.info("[AutoTrain] Rule approved: %s", rule_id)
    return {"status": "approved", "rule_id": rule_id}


@router.post("/rules/{rule_id}/reject")
async def reject_rule(rule_id: str, reviewed_by: Optional[str] = Query(None)) -> Dict[str, Any]:
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT feedback_id FROM generated_rules WHERE rule_id = ?", (rule_id,))
    row = cur.fetchone()
    if not row:
        conn.close()
        raise HTTPException(status_code=404, detail="Rule not found")

    cur.execute(
        """
        UPDATE generated_rules
        SET status = 'rejected', reviewed_by = ?, reviewed_at = ?
        WHERE rule_id = ?
        """,
        (reviewed_by or "admin", datetime.now().isoformat(), rule_id),
    )
    cur.execute(
        """
        UPDATE feedbacks
        SET status = 'rejected', processed_at = ?
        WHERE feedback_id = ?
        """,
        (datetime.now().isoformat(), row["feedback_id"]),
    )
    conn.commit()
    conn.close()
    return {"status": "rejected", "rule_id": rule_id}


@router.post("/rules/{rule_id}/deploy")
async def deploy_rule(rule_id: str, deployed_by: Optional[str] = Query(None)) -> Dict[str, Any]:
    conn = get_db()
    cur = conn.cursor()
    cur.execute(
        """
        SELECT *
        FROM generated_rules
        WHERE rule_id = ? AND status = 'approved'
        """,
        (rule_id,),
    )
    rule = cur.fetchone()
    if not rule:
        conn.close()
        raise HTTPException(status_code=400, detail="Rule not found or not approved")

    rule_dict = dict(rule)
    validation_errors = validate_rule(rule_dict)
    if validation_errors:
        conn.close()
        raise HTTPException(
            status_code=422,
            detail={"message": "Rule failed validation", "errors": validation_errors},
        )

    deploy_result = _deploy_rule_to_target(rule_dict)
    cur.execute(
        """
        INSERT INTO deployments (rule_id, deployed_by, version_before, version_after, rollback_of)
        VALUES (?, ?, ?, ?, ?)
        """,
        (
            rule_id,
            deployed_by or "admin",
            (rule_dict.get("old_rule") or "")[:200] or None,
            rule_dict["new_rule"][:200],
            None,
        ),
    )
    cur.execute("UPDATE generated_rules SET status = 'deployed' WHERE rule_id = ?", (rule_id,))
    conn.commit()
    conn.close()

    logger.info("[AutoTrain] Rule deployed: %s", rule_id)
    return {
        "status": "deployed",
        "rule_id": rule_id,
        "module": rule_dict["module_name"],
        "deployed_at": datetime.now().isoformat(),
        "target_file": deploy_result["target_file"],
        "backup_file": deploy_result["backup_file"],
    }


@router.get("/status", response_model=AutoTrainStatus)
async def get_status() -> AutoTrainStatus:
    conn = get_db()
    cur = conn.cursor()

    cur.execute("SELECT COUNT(*) FROM feedbacks")
    total_feedbacks = cur.fetchone()[0]
    cur.execute("SELECT COUNT(*) FROM feedbacks WHERE status = 'pending'")
    pending_feedbacks = cur.fetchone()[0]

    cur.execute("SELECT COUNT(*) FROM generated_rules")
    total_rules = cur.fetchone()[0]
    cur.execute("SELECT COUNT(*) FROM generated_rules WHERE status = 'pending'")
    pending_rules = cur.fetchone()[0]
    cur.execute("SELECT COUNT(*) FROM generated_rules WHERE status = 'deployed'")
    deployed_rules = cur.fetchone()[0]

    cur.execute(
        """
        SELECT 'feedback' AS type, feedback_id AS ref_id, created_at AS time, status
        FROM feedbacks
        UNION ALL
        SELECT 'rule' AS type, rule_id AS ref_id, created_at AS time, status
        FROM generated_rules
        UNION ALL
        SELECT 'deployment' AS type, rule_id AS ref_id, deployed_at AS time, 'deployed' AS status
        FROM deployments
        ORDER BY time DESC
        LIMIT 10
        """
    )
    recent_activity = [dict(row) for row in cur.fetchall()]
    conn.close()

    return AutoTrainStatus(
        total_feedbacks=total_feedbacks,
        pending_feedbacks=pending_feedbacks,
        total_rules=total_rules,
        pending_rules=pending_rules,
        deployed_rules=deployed_rules,
        recent_activity=recent_activity,
    )


@router.post("/run-all")
async def run_all_pending() -> Dict[str, Any]:
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT feedback_id FROM feedbacks WHERE status = 'pending'")
    pending = [row["feedback_id"] for row in cur.fetchall()]
    conn.close()

    results: List[Dict[str, Any]] = []
    for feedback_id in pending:
        try:
            result = await generate_rule(feedback_id)
            results.append(
                {
                    "feedback_id": feedback_id,
                    "rule_id": result["rule_id"],
                    "status": result["status"],
                }
            )
        except HTTPException as exc:
            results.append({"feedback_id": feedback_id, "error": exc.detail})
        except Exception as exc:  # pragma: no cover
            results.append({"feedback_id": feedback_id, "error": str(exc)})

    return {
        "processed": len(pending),
        "results": results,
        "note": "All pending feedbacks processed. Review at /autotrain/rules.",
    }
