from dotenv import load_dotenv
load_dotenv()

from datetime import datetime, timezone, timedelta
from contextlib import asynccontextmanager
import os

import bcrypt
import jwt
from fastapi import FastAPI, Depends, HTTPException, Request, Response, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from db import GalleryItem, PerformanceLog, get_session, init_db


@asynccontextmanager
async def lifespan(app: FastAPI):
    await init_db()
    yield


app = FastAPI(title="PLTMG Sumbawa II Portal API", version="3.0.0", lifespan=lifespan)

origins = [item.strip() for item in os.environ.get("CORS_ORIGINS", "").split(",") if item.strip() and item.strip() != "*"]
if not origins:
    origins = [os.environ.get("FRONTEND_URL", "https://pltmg-sumbawa.preview.emergentagent.com")]
app.add_middleware(CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"])

JWT_SECRET = os.environ["JWT_SECRET"]
ADMIN_EMAIL = os.environ["ADMIN_EMAIL"]
ADMIN_PASSWORD = os.environ["ADMIN_PASSWORD"]
ADMIN_PASSWORD_HASH = bcrypt.hashpw(ADMIN_PASSWORD.encode(), bcrypt.gensalt())
JWT_ALGORITHM = "HS256"


class LoginInput(BaseModel):
    email: str
    password: str


class GalleryInput(BaseModel):
    title: str = Field(min_length=2, max_length=120)
    caption: str = Field(min_length=2, max_length=500)
    image_url: str = Field(min_length=5)
    category: str = "Operasional"


class PerformanceInput(BaseModel):
    date: str
    sfc: float = Field(ge=0)
    sloc: float = Field(ge=0)
    net_kwh: float = Field(ge=0)
    fuel_consumption_liters: float = Field(ge=0)
    running_hours_engine1: float = Field(ge=0)
    running_hours_engine2: float = Field(ge=0)
    running_hours_engine3: float = Field(ge=0)

    def with_derived(self) -> dict:
        """Selalu turunkan beban rata-rata harian dari produksi netto / 24 jam
        agar backend menjadi satu-satunya sumber kebenaran (tidak bergantung
        pada nilai yang dikirim klien)."""
        data = self.model_dump()
        data["avg_daily_load_kw"] = round(self.net_kwh / 24)
        return data


login_attempts: dict = {}


def token_for(email: str) -> str:
    return jwt.encode(
        {"sub": email, "role": "admin", "exp": datetime.now(timezone.utc) + timedelta(hours=8)},
        JWT_SECRET,
        algorithm=JWT_ALGORITHM,
    )


async def require_admin(request: Request):
    token = request.cookies.get("pltmg_admin")
    if not token:
        header = request.headers.get("Authorization", "")
        token = header.replace("Bearer ", "") if header.startswith("Bearer ") else ""
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
        if payload.get("role") != "admin" or payload.get("sub") != ADMIN_EMAIL:
            raise ValueError()
        return payload
    except (jwt.PyJWTError, ValueError):
        raise HTTPException(status_code=401, detail="Sesi admin tidak valid atau sudah berakhir.")


@app.get("/")
def root():
    return {"message": "PLTMG Sumbawa II 30 MW Portal API"}


@app.post("/api/auth/login")
def login(data: LoginInput, request: Request, response: Response):
    # Key on normalised email only — through the ingress the peer IP rotates
    # between proxy replicas, which would otherwise bypass the 5-strike lock.
    identifier = data.email.lower().strip()
    attempt = login_attempts.get(identifier, {"count": 0, "locked_until": datetime.min.replace(tzinfo=timezone.utc)})
    if datetime.now(timezone.utc) < attempt["locked_until"]:
        raise HTTPException(status_code=429, detail="Terlalu banyak percobaan. Coba lagi dalam 15 menit.")
    if data.email.lower().strip() != ADMIN_EMAIL.lower() or not bcrypt.checkpw(data.password.encode(), ADMIN_PASSWORD_HASH):
        attempt["count"] += 1
        if attempt["count"] >= 5:
            attempt["locked_until"] = datetime.now(timezone.utc) + timedelta(minutes=15)
            attempt["count"] = 0
        login_attempts[identifier] = attempt
        raise HTTPException(status_code=401, detail="Email atau password admin salah.")
    login_attempts.pop(identifier, None)
    response.set_cookie("pltmg_admin", token_for(ADMIN_EMAIL), httponly=True, secure=True, samesite="none", max_age=28800, path="/")
    return {"status": "success", "email": ADMIN_EMAIL, "role": "admin"}


@app.get("/api/auth/me")
async def me(user=Depends(require_admin)):
    return {"email": user["sub"], "role": "admin"}


@app.post("/api/auth/logout")
def logout(response: Response):
    response.delete_cookie("pltmg_admin", path="/")
    return {"status": "success"}


# ---------------- Gallery CRUD ----------------

@app.get("/api/gallery")
async def get_gallery(session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(GalleryItem).order_by(GalleryItem.id.asc()))
    return [item.to_dict() for item in result.scalars().all()]


@app.post("/api/gallery")
async def create_gallery(data: GalleryInput, session: AsyncSession = Depends(get_session), user=Depends(require_admin)):
    item = GalleryItem(**data.model_dump())
    session.add(item)
    await session.commit()
    await session.refresh(item)
    return item.to_dict()


@app.put("/api/gallery/{item_id}")
async def update_gallery(item_id: int, data: GalleryInput, session: AsyncSession = Depends(get_session), user=Depends(require_admin)):
    item = await session.get(GalleryItem, item_id)
    if not item:
        raise HTTPException(404, "Foto tidak ditemukan.")
    for key, value in data.model_dump().items():
        setattr(item, key, value)
    await session.commit()
    await session.refresh(item)
    return item.to_dict()


@app.delete("/api/gallery/{item_id}")
async def delete_gallery(item_id: int, session: AsyncSession = Depends(get_session), user=Depends(require_admin)):
    item = await session.get(GalleryItem, item_id)
    if not item:
        raise HTTPException(404, "Foto tidak ditemukan.")
    await session.delete(item)
    await session.commit()
    return {"status": "deleted"}


@app.post("/api/gallery/upload")
async def upload_gallery(file: UploadFile = File(...), user=Depends(require_admin)):
    if not file.content_type or not file.content_type.startswith("image/"):
        raise HTTPException(400, "File harus berupa gambar.")
    content = await file.read()
    if len(content) > 5 * 1024 * 1024:
        raise HTTPException(400, "Ukuran foto maksimal 5 MB.")
    import base64
    return {"image_url": f"data:{file.content_type};base64,{base64.b64encode(content).decode()}"}


# ---------------- Performance CRUD ----------------

@app.get("/api/performance")
async def get_performance(session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(PerformanceLog).order_by(PerformanceLog.date.asc(), PerformanceLog.id.asc()))
    return [item.to_dict() for item in result.scalars().all()]


@app.post("/api/performance")
async def create_performance(data: PerformanceInput, session: AsyncSession = Depends(get_session), user=Depends(require_admin)):
    item = PerformanceLog(**data.with_derived())
    session.add(item)
    await session.commit()
    await session.refresh(item)
    return item.to_dict()


@app.put("/api/performance/{record_id}")
async def update_performance(record_id: int, data: PerformanceInput, session: AsyncSession = Depends(get_session), user=Depends(require_admin)):
    item = await session.get(PerformanceLog, record_id)
    if not item:
        raise HTTPException(404, "Data kinerja tidak ditemukan.")
    for key, value in data.with_derived().items():
        setattr(item, key, value)
    await session.commit()
    await session.refresh(item)
    return item.to_dict()


@app.delete("/api/performance/{record_id}")
async def delete_performance(record_id: int, session: AsyncSession = Depends(get_session), user=Depends(require_admin)):
    item = await session.get(PerformanceLog, record_id)
    if not item:
        raise HTTPException(404, "Data kinerja tidak ditemukan.")
    await session.delete(item)
    await session.commit()
    return {"status": "deleted"}
