from fastapi import FastAPI, UploadFile, File, Form, BackgroundTasks, HTTPException, Depends
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from typing import List, Optional
import shutil
import os
import uuid
from pathlib import Path
import zipfile
import io

# Import local modules
from .models import Base, Order, OrderItem, Photo, PaperType, FillMode, OrderStatus
from .image_utils import image_utils

# Database Setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Create Tables
Base.metadata.create_all(bind=engine)

# App Setup
app = FastAPI(title="Showing Photo Service", version="1.1.0")

# CORS Setup
origins = [
    "http://localhost:3000",
    "https://littlestory.co.kr", # Production / Test Server
    "*"
]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Storage Setup - Split directories
BASE_UPLOAD_DIR = Path("uploads")
ORIGINAL_DIR = BASE_UPLOAD_DIR / "original"
PROCESSED_DIR = BASE_UPLOAD_DIR / "processed"

ORIGINAL_DIR.mkdir(parents=True, exist_ok=True)
PROCESSED_DIR.mkdir(parents=True, exist_ok=True)

# Mount Static Files
app.mount("/static", StaticFiles(directory="uploads"), name="static")

# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# --- Background Task ---
def process_image_task(photo_id: str, original_path: str, processed_path: str, fill_mode: str, print_date: bool, date_text: Optional[str] = None):
    """
    Reads from original_path, processes, saves to processed_path.
    Updates DB with processed_path is omitted here as we set it upfront, 
    but in a real scenario we might update a 'status' flag in DB.
    """
    try:
        # Default ratio 4:6
        process_ratio = 4/6
        
        image_utils.process_and_save(
            input_path=original_path,
            output_path=processed_path,
            fill_mode=fill_mode,
            target_ratio_w_h=process_ratio,
            do_enhance=True,
            print_date=print_date,
            date_text=date_text
        )
        print(f"[{photo_id}] Processed and saved to {processed_path}")
    except Exception as e:
        print(f"[{photo_id}] Error in processing: {e}")

# --- Endpoints ---

@app.get("/")
def read_root():
    return {"message": "Showing Photo Service API v1.1"}

@app.post("/api/v1/orders")
def create_order(user_id: Optional[str] = None, db: Session = Depends(get_db)):
    # Using user_id as a proxy for Customer Name for now
    new_order = Order(user_id=user_id if user_id else "Guest")
    db.add(new_order)
    db.commit()
    db.refresh(new_order)
    return {"order_id": new_order.id, "status": new_order.status}

@app.post("/api/v1/orders/{order_id}/items")
def add_order_item(order_id: str, product_code: str, quantity: int = 1, paper_type: PaperType = PaperType.GLOSSY, print_date: bool = False, db: Session = Depends(get_db)):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    
    new_item = OrderItem(
        order_id=order_id,
        product_code=product_code,
        quantity=quantity,
        paper_type=paper_type,
        print_date=print_date
    )
    db.add(new_item)
    db.commit()
    db.refresh(new_item)
    return {"item_id": new_item.id, "paper_type": new_item.paper_type}

@app.post("/api/v1/items/{item_id}/photos/upload")
async def upload_photos(
    item_id: str,
    background_tasks: BackgroundTasks,
    files: List[UploadFile] = File(...),
    fill_mode: FillMode = Form(FillMode.PAPER_FULL),
    print_date: bool = Form(False),
    db: Session = Depends(get_db)
):
    item = db.query(OrderItem).filter(OrderItem.id == item_id).first()
    if not item:
        raise HTTPException(status_code=404, detail="Order Item not found")

    uploaded_photos = []
    
    should_print_date = print_date or item.print_date
    
    for file in files:
        photo_id = str(uuid.uuid4())
        ext = os.path.splitext(file.filename)[1]
        if not ext: ext = ".jpg"
        
        # 1. Save to ORIGINAL_DIR
        original_filename = f"{photo_id}_orig{ext}"
        original_path = ORIGINAL_DIR / original_filename
        
        processed_filename = f"{photo_id}_proc.jpg" # Always converting to jpg/outputting jpg from utils
        processed_path = PROCESSED_DIR / processed_filename
        
        with open(original_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
            
        # 2. DB Record with both paths
        new_photo = Photo(
            id=photo_id,
            order_item_id=item_id,
            original_path=str(original_path),
            processed_path=str(processed_path),
            original_filename=file.filename,
            fill_mode=fill_mode,
            is_enhanced=True, # Default
            file_size=file.size if file.size else 0
        )
        db.add(new_photo)
        
        uploaded_photos.append({
            "photo_id": photo_id,
            "url": f"/static/processed/{processed_filename}" # Frontend views processed
        })
        
        # 3. Schedule Task
        background_tasks.add_task(
            process_image_task,
            photo_id=photo_id,
            original_path=str(original_path),
            processed_path=str(processed_path),
            fill_mode=fill_mode,
            print_date=should_print_date
        )
    
    db.commit()
    
    return {
        "message": "Upload successful",
        "uploaded_count": len(uploaded_photos),
        "photos": uploaded_photos
    }

@app.get("/api/v1/admin/orders/{order_id}/download")
def download_order_photos(order_id: str, db: Session = Depends(get_db)):
    """
    Downloads images with renamed convention: {Customer}_{Option}_{Index}.jpg
    Uses PROCESSED images.
    """
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
        
    order.status = OrderStatus.DOWNLOADING
    db.commit()
    
    # Naming convention Components
    customer_name = order.user_id if order.user_id else "NoName"
    # Minimize sanitize
    customer_name = "".join([c for c in customer_name if c.isalnum() or c in ('-', '_')])
    
    zip_filename = f"{customer_name}_order_{order_id[:8]}.zip"
    zip_path = BASE_UPLOAD_DIR / zip_filename
    
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        global_idx = 1
        for item in order.items:
            # Option string e.g. "Glossy_4x6"
            option_str = f"{item.paper_type.value}_{item.product_code}"
            
            for photo in item.photos:
                # Use processed path if exists, else original fallback (or skip)
                target_path = Path(photo.processed_path)
                if not target_path.exists():
                     # Fallback to original if processing not done yet? 
                     # Or maybe just wait? For now, fallback to original to ensure file delivery.
                     target_path = Path(photo.original_path)
                
                if target_path.exists():
                    ext = target_path.suffix
                    # Renaming Rule: {Customer}_{Option}_{Index}
                    new_filename = f"{customer_name}_{option_str}_{global_idx:03d}{ext}"
                    zf.write(target_path, arcname=new_filename)
                    global_idx += 1
    
    return FileResponse(
        zip_path,
        media_type="application/zip",
        filename=zip_filename
    )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
