"""Randevu Backend - SQL (MySQL/MariaDB) with SQLAlchemy async + JWT auth + file uploads."""
from fastapi import FastAPI, APIRouter, HTTPException, Depends, UploadFile, File
from fastapi.security import OAuth2PasswordBearer
from fastapi.staticfiles import StaticFiles
from starlette.middleware.cors import CORSMiddleware
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from passlib.context import CryptContext
from jose import jwt, JWTError
import os
import logging
import uuid
import shutil
from pathlib import Path
from pydantic import BaseModel
from typing import List, Optional, Any, Dict
from datetime import datetime, timezone, timedelta

from database import engine, Base, SessionLocal, get_session
from models import (
    Admin, Setting, Pill, TimelineItem, Service, Project, BlogPost, Doctor,
    ContactSubmission, ENTITY_MAP,
)
from seed_data import seed_all

# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
ROOT_DIR = Path(__file__).parent
UPLOAD_DIR = ROOT_DIR / "uploads"
UPLOAD_DIR.mkdir(exist_ok=True)

JWT_SECRET = os.environ.get("JWT_SECRET_KEY", "randevu-abbasov-secret-key-2025-change-in-prod")
JWT_ALG = "HS256"
JWT_EXP_HOURS = 24 * 7

DEFAULT_ADMIN_USER = os.environ.get("ADMIN_USERNAME", "admin")
DEFAULT_ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "Randevu2025!")

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/admin/login", auto_error=False)


def hash_password(pw: str) -> str:
    return pwd_context.hash(pw)


def verify_password(pw: str, hashed: str) -> bool:
    try:
        return pwd_context.verify(pw, hashed)
    except Exception:
        return False


def create_access_token(subject: str) -> str:
    expires = datetime.now(timezone.utc) + timedelta(hours=JWT_EXP_HOURS)
    return jwt.encode({"sub": subject, "exp": expires}, JWT_SECRET, algorithm=JWT_ALG)


async def get_current_admin(
    token: Optional[str] = Depends(oauth2_scheme),
    session: AsyncSession = Depends(get_session),
) -> str:
    if not token:
        raise HTTPException(status_code=401, detail="Autentifikasiya tələb olunur")
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
        username: Optional[str] = payload.get("sub")
        if not username:
            raise HTTPException(status_code=401, detail="Keçərsiz token")
        res = await session.execute(select(Admin).where(Admin.username == username))
        if res.scalar_one_or_none() is None:
            raise HTTPException(status_code=401, detail="Admin tapılmadı")
        return username
    except JWTError:
        raise HTTPException(status_code=401, detail="Keçərsiz və ya vaxtı keçmiş token")


# ---------------------------------------------------------------------------
# Pydantic schemas
# ---------------------------------------------------------------------------
class LoginRequest(BaseModel):
    username: str
    password: str


class TokenResponse(BaseModel):
    access_token: str
    token_type: str = "bearer"
    username: str


class PillCreate(BaseModel):
    side: str
    label: str
    order: int = 0


class TimelineCreate(BaseModel):
    period: str
    title: str
    bullets: List[str] = []
    order: int = 0


class ServiceCreate(BaseModel):
    title: str
    content: str = ""
    order: int = 0


class ProjectCreate(BaseModel):
    title: str
    address: str = ""
    street: str = ""
    image: str = ""
    order: int = 0


class BlogCreate(BaseModel):
    title: str
    content: str = ""
    image: str = ""
    order: int = 0


class DoctorCreate(BaseModel):
    name: str
    specialty: str = ""
    education: str = ""
    diseases: str = ""
    image: str = ""
    order: int = 0


class ContactCreate(BaseModel):
    surname: str = ""
    name: str = ""
    fatherName: str = ""
    birth: str = ""
    phone: str = ""
    city: str = ""
    specialty: str = ""
    workplace: str = ""
    position: str = ""


class SettingUpdate(BaseModel):
    value: str


CREATE_SCHEMAS = {
    "pills": PillCreate,
    "timeline": TimelineCreate,
    "services": ServiceCreate,
    "projects": ProjectCreate,
    "blog": BlogCreate,
    "doctors": DoctorCreate,
}

# ---------------------------------------------------------------------------
app = FastAPI(title="Randevu API")
api = APIRouter(prefix="/api")
app.mount("/api/uploads", StaticFiles(directory=str(UPLOAD_DIR)), name="uploads")


def to_dict(obj: Any) -> Dict[str, Any]:
    if obj is None:
        return {}
    return {c.name: getattr(obj, c.name) for c in obj.__table__.columns}


@api.get("/")
async def root() -> Dict[str, str]:
    return {"message": "Randevu API"}


@api.post("/admin/login", response_model=TokenResponse)
async def login(payload: LoginRequest, session: AsyncSession = Depends(get_session)) -> TokenResponse:
    res = await session.execute(select(Admin).where(Admin.username == payload.username))
    admin = res.scalar_one_or_none()
    if not admin or not verify_password(payload.password, admin.password_hash):
        raise HTTPException(status_code=401, detail="İstifadəçi adı və ya şifrə səhvdir")
    return TokenResponse(access_token=create_access_token(payload.username), username=payload.username)


@api.get("/admin/me")
async def me(current: str = Depends(get_current_admin)) -> Dict[str, str]:
    return {"username": current}


@api.post("/admin/upload")
async def upload_file(file: UploadFile = File(...), current: str = Depends(get_current_admin)) -> Dict[str, str]:
    ext = Path(file.filename or "").suffix.lower() or ".bin"
    if ext not in {".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"}:
        raise HTTPException(status_code=400, detail="Yalnız şəkil faylları yüklənə bilər")
    fname = f"{uuid.uuid4()}{ext}"
    dest = UPLOAD_DIR / fname
    with dest.open("wb") as f:
        shutil.copyfileobj(file.file, f)
    return {"url": f"/api/uploads/{fname}", "filename": fname}


# ---------- Settings ----------
@api.get("/public/settings")
async def get_public_settings(session: AsyncSession = Depends(get_session)) -> Dict[str, str]:
    res = await session.execute(select(Setting))
    return {row.key: row.value for row in res.scalars().all()}


@api.get("/admin/settings")
async def get_admin_settings(current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> Dict[str, str]:
    res = await session.execute(select(Setting))
    return {row.key: row.value for row in res.scalars().all()}


@api.put("/admin/settings/{key}")
async def update_setting(key: str, payload: SettingUpdate, current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> Dict[str, str]:
    res = await session.execute(select(Setting).where(Setting.key == key))
    row = res.scalar_one_or_none()
    if row is None:
        row = Setting(key=key, value=payload.value)
        session.add(row)
    else:
        row.value = payload.value
    await session.commit()
    return {"key": key, "value": payload.value}


# ---------- Generic CRUD ----------
def make_crud(name: str, model_cls: Any, create_cls: Any) -> None:
    async def list_all(session: AsyncSession = Depends(get_session)) -> List[Dict[str, Any]]:
        res = await session.execute(select(model_cls).order_by(model_cls.order))
        return [to_dict(o) for o in res.scalars().all()]

    async def create(payload, session: AsyncSession = Depends(get_session), current: str = Depends(get_current_admin)) -> Dict[str, Any]:
        obj = model_cls(**payload.model_dump())
        session.add(obj)
        await session.commit()
        await session.refresh(obj)
        return to_dict(obj)

    async def update_item(item_id: str, payload, session: AsyncSession = Depends(get_session), current: str = Depends(get_current_admin)) -> Dict[str, Any]:
        res = await session.execute(select(model_cls).where(model_cls.id == item_id))
        obj = res.scalar_one_or_none()
        if obj is None:
            raise HTTPException(status_code=404, detail="Tapılmadı")
        for k, v in payload.model_dump().items():
            setattr(obj, k, v)
        await session.commit()
        await session.refresh(obj)
        return to_dict(obj)

    async def delete_item(item_id: str, session: AsyncSession = Depends(get_session), current: str = Depends(get_current_admin)) -> Dict[str, str]:
        res = await session.execute(select(model_cls).where(model_cls.id == item_id))
        obj = res.scalar_one_or_none()
        if obj is None:
            raise HTTPException(status_code=404, detail="Tapılmadı")
        await session.delete(obj)
        await session.commit()
        return {"status": "ok"}

    create.__annotations__["payload"] = create_cls
    update_item.__annotations__["payload"] = create_cls

    api.add_api_route(f"/public/{name}", list_all, methods=["GET"])
    api.add_api_route(f"/admin/{name}", list_all, methods=["GET"], dependencies=[Depends(get_current_admin)])
    api.add_api_route(f"/admin/{name}", create, methods=["POST"])
    api.add_api_route(f"/admin/{name}/{{item_id}}", update_item, methods=["PUT"])
    api.add_api_route(f"/admin/{name}/{{item_id}}", delete_item, methods=["DELETE"])


for _name, _model in ENTITY_MAP.items():
    make_crud(_name, _model, CREATE_SCHEMAS[_name])


# ---------- Contact submissions ----------
@api.post("/public/contact")
async def submit_contact(payload: ContactCreate, session: AsyncSession = Depends(get_session)) -> Dict[str, str]:
    obj = ContactSubmission(**payload.model_dump())
    session.add(obj)
    await session.commit()
    await session.refresh(obj)
    return {"status": "ok", "id": obj.id}


@api.get("/admin/contacts")
async def list_contacts(current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> List[Dict[str, Any]]:
    res = await session.execute(select(ContactSubmission).order_by(ContactSubmission.createdAt.desc()))
    return [to_dict(o) for o in res.scalars().all()]


@api.delete("/admin/contacts/{item_id}")
async def delete_contact(item_id: str, current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> Dict[str, str]:
    res = await session.execute(select(ContactSubmission).where(ContactSubmission.id == item_id))
    obj = res.scalar_one_or_none()
    if obj is None:
        raise HTTPException(status_code=404, detail="Tapılmadı")
    await session.delete(obj)
    await session.commit()
    return {"status": "ok"}


# ---------- Chat (AI + admin hybrid) ----------
from models import ChatSession, ChatMessage
from chat_service import generate_reply
from datetime import datetime, timezone


class ChatMessagePayload(BaseModel):
    session_id: Optional[str] = None
    message: str
    visitor_name: str = ""


class AdminReplyPayload(BaseModel):
    message: str


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


@api.get("/public/chat/session/{session_id}")
async def get_chat_session(session_id: str, session: AsyncSession = Depends(get_session)) -> Dict[str, Any]:
    res = await session.execute(select(ChatMessage).where(ChatMessage.session_id == session_id).order_by(ChatMessage.createdAt))
    msgs = [to_dict(m) for m in res.scalars().all()]
    return {"session_id": session_id, "messages": msgs}


@api.post("/public/chat/message")
async def post_chat_message(payload: ChatMessagePayload, session: AsyncSession = Depends(get_session)) -> Dict[str, Any]:
    if not payload.message.strip():
        raise HTTPException(status_code=400, detail="Boş mesaj göndərmək olmaz")

    # Ensure session exists
    sid = payload.session_id
    chat_session: Optional[ChatSession] = None
    if sid:
        chat_session = (await session.execute(select(ChatSession).where(ChatSession.id == sid))).scalar_one_or_none()
    if chat_session is None:
        chat_session = ChatSession(visitor_name=payload.visitor_name or "")
        if sid:
            chat_session.id = sid
        session.add(chat_session)
        await session.flush()
        sid = chat_session.id
    else:
        chat_session.lastActivity = _now_iso()
        if payload.visitor_name and not chat_session.visitor_name:
            chat_session.visitor_name = payload.visitor_name

    # Store user message
    user_msg = ChatMessage(session_id=sid, role="user", content=payload.message.strip())
    session.add(user_msg)
    await session.commit()
    await session.refresh(user_msg)

    # Generate AI reply
    reply_text = await generate_reply(session, sid, payload.message.strip())
    ai_msg_dict: Optional[Dict[str, Any]] = None
    if reply_text:
        ai_msg = ChatMessage(session_id=sid, role="assistant", content=reply_text)
        session.add(ai_msg)
        chat_session.lastActivity = _now_iso()
        await session.commit()
        await session.refresh(ai_msg)
        ai_msg_dict = to_dict(ai_msg)

    return {
        "session_id": sid,
        "user_message": to_dict(user_msg),
        "ai_message": ai_msg_dict,
    }


@api.get("/admin/chats")
async def list_chat_sessions(current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> List[Dict[str, Any]]:
    res = await session.execute(select(ChatSession).order_by(ChatSession.lastActivity.desc()))
    sessions = res.scalars().all()
    out: List[Dict[str, Any]] = []
    for s in sessions:
        # Get last message + count
        msgs_res = await session.execute(select(ChatMessage).where(ChatMessage.session_id == s.id).order_by(ChatMessage.createdAt.desc()))
        msgs = msgs_res.scalars().all()
        last = msgs[0] if msgs else None
        out.append({
            **to_dict(s),
            "message_count": len(msgs),
            "last_message": to_dict(last) if last else None,
        })
    return out


@api.get("/admin/chats/{session_id}")
async def get_chat_detail(session_id: str, current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> Dict[str, Any]:
    s = (await session.execute(select(ChatSession).where(ChatSession.id == session_id))).scalar_one_or_none()
    if s is None:
        raise HTTPException(status_code=404, detail="Chat sessiyası tapılmadı")
    msgs = (await session.execute(select(ChatMessage).where(ChatMessage.session_id == session_id).order_by(ChatMessage.createdAt))).scalars().all()
    return {"session": to_dict(s), "messages": [to_dict(m) for m in msgs]}


@api.post("/admin/chats/{session_id}/message")
async def admin_reply(session_id: str, payload: AdminReplyPayload, current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> Dict[str, Any]:
    s = (await session.execute(select(ChatSession).where(ChatSession.id == session_id))).scalar_one_or_none()
    if s is None:
        raise HTTPException(status_code=404, detail="Chat sessiyası tapılmadı")
    if not payload.message.strip():
        raise HTTPException(status_code=400, detail="Boş mesaj göndərmək olmaz")
    msg = ChatMessage(session_id=session_id, role="admin", content=payload.message.strip())
    session.add(msg)
    s.lastActivity = _now_iso()
    await session.commit()
    await session.refresh(msg)
    return to_dict(msg)


@api.delete("/admin/chats/{session_id}")
async def delete_chat(session_id: str, current: str = Depends(get_current_admin), session: AsyncSession = Depends(get_session)) -> Dict[str, str]:
    s = (await session.execute(select(ChatSession).where(ChatSession.id == session_id))).scalar_one_or_none()
    if s is None:
        raise HTTPException(status_code=404, detail="Tapılmadı")
    # Delete messages first
    msgs = (await session.execute(select(ChatMessage).where(ChatMessage.session_id == session_id))).scalars().all()
    for m in msgs:
        await session.delete(m)
    await session.delete(s)
    await session.commit()
    return {"status": "ok"}


app.include_router(api)
app.add_middleware(
    CORSMiddleware,
    allow_credentials=True,
    allow_origins=os.environ.get("CORS_ORIGINS", "*").split(","),
    allow_methods=["*"],
    allow_headers=["*"],
)

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)


@app.on_event("startup")
async def on_startup() -> None:
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    async with SessionLocal() as session:
        res = await session.execute(select(Admin).where(Admin.username == DEFAULT_ADMIN_USER))
        if res.scalar_one_or_none() is None:
            session.add(Admin(username=DEFAULT_ADMIN_USER, password_hash=hash_password(DEFAULT_ADMIN_PASSWORD)))
            await session.commit()
            logger.info(f"Seeded default admin: {DEFAULT_ADMIN_USER}")
        await seed_all(session)


@app.on_event("shutdown")
async def on_shutdown() -> None:
    await engine.dispose()
