"""
Multi-Tool SaaS - Main FastAPI Application
"""
import os, re, uuid, shutil
from pathlib import Path
from datetime import datetime
from fastapi import FastAPI, UploadFile, File, Form, Request, HTTPException, BackgroundTasks
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import tools.image_compressor as ic
import tools.image_resizer as ir
import tools.color_extractor as ce
import tools.qr_generator as qg
import tools.text_tools as tt

app = FastAPI(title="ToolForge", version="1.0.0")
BASE_DIR = Path(__file__).parent
UPLOAD_DIR = BASE_DIR / "uploads"
STATIC_DIR = BASE_DIR / "static"
MAX_FILE_SIZE = 50 * 1024 * 1024
UPLOAD_DIR.mkdir(exist_ok=True)
STATIC_DIR.mkdir(exist_ok=True)
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))

def sanitize_filename(name: str) -> str:
    name = os.path.basename(name)
    name = re.sub(r'[<>:"/\\|?*\x00-\x1f]', '_', name)
    name = re.sub(r'\.\.', '_', name)
    if not name or name in ('.', '..'):
        name = f"file_{uuid.uuid4().hex[:8]}"
    return name

def validate_file(file: UploadFile, allowed_exts: set[str]) -> None:
    if not file.filename:
        raise HTTPException(400, "No filename provided.")
    ext = Path(file.filename).suffix.lower()
    if ext not in allowed_exts:
        raise HTTPException(400, f"Unsupported file type '{ext}'. Allowed: {', '.join(sorted(allowed_exts))}")

def cleanup(paths: list[Path]) -> None:
    for p in paths:
        if p and p.exists():
            try:
                if p.is_dir():
                    shutil.rmtree(p, ignore_errors=True)
                else:
                    p.unlink(missing_ok=True)
            except OSError:
                pass

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    tools_list = [
        {"name": "Image Compressor", "slug": "compress", "desc": "Compress PNG, JPG, WebP images up to 80%", "icon": "📦"},
        {"name": "Image Resizer", "slug": "resize", "desc": "Resize for Instagram, Twitter, LinkedIn & more", "icon": "📐"},
        {"name": "Color Palette Generator", "slug": "palette", "desc": "Extract a beautiful color palette from any image", "icon": "🎨"},
        {"name": "QR Code Generator", "slug": "qr", "desc": "Generate QR codes from text, URLs, or Wi-Fi config", "icon": "📱"},
        {"name": "Text Tools", "slug": "text", "desc": "Word/char counter, case converter, string inspector", "icon": "✏️"},
    ]
    return templates.TemplateResponse(request, "index.html", {"request": request, "tools": tools_list, "year": datetime.now().year})

@app.get("/tool/compress", response_class=HTMLResponse)
async def compress_page(request: Request):
    return templates.TemplateResponse(request, "tool_compress.html", {"request": request, "year": datetime.now().year})

@app.post("/api/compress")
async def api_compress(file: UploadFile = File(...), quality: int = Form(70), bg: BackgroundTasks = BackgroundTasks()):
    validate_file(file, {".png", ".jpg", ".jpeg", ".webp"})
    contents = await file.read()
    if len(contents) > MAX_FILE_SIZE:
        raise HTTPException(413, f"File too large. Max {MAX_FILE_SIZE // (1024*1024)} MB.")
    if len(contents) == 0:
        raise HTTPException(400, "Empty file uploaded.")
    in_path = UPLOAD_DIR / f"{uuid.uuid4().hex}_{sanitize_filename(file.filename)}"
    out_path = in_path.with_suffix(".compressed" + in_path.suffix)
    in_path.write_bytes(contents)
    result = ic.compress_image(str(in_path), str(out_path), quality=quality)
    bg.add_task(cleanup, [in_path, out_path])
    return FileResponse(str(out_path), media_type=result["mime"], filename=f"compressed_{sanitize_filename(file.filename)}")

PRESETS = {
    "instagram_square": (1080, 1080), "instagram_portrait": (1080, 1350),
    "twitter": (1200, 675), "linkedin": (1200, 627),
    "facebook": (1200, 630), "pinterest": (1000, 1500),
    "youtube": (1280, 720), "og_image": (1200, 630),
}

@app.get("/tool/resize", response_class=HTMLResponse)
async def resize_page(request: Request):
    return templates.TemplateResponse(request, "tool_resize.html", {"request": request, "presets": PRESETS, "year": datetime.now().year})

@app.post("/api/resize")
async def api_resize(file: UploadFile = File(...), width: int = Form(...), height: int = Form(...), keep_aspect: bool = Form(True), bg: BackgroundTasks = BackgroundTasks()):
    validate_file(file, {".png", ".jpg", ".jpeg", ".webp"})
    contents = await file.read()
    if len(contents) > MAX_FILE_SIZE:
        raise HTTPException(413, f"File too large. Max {MAX_FILE_SIZE // (1024*1024)} MB.")
    if width < 1 or width > 10000 or height < 1 or height > 10000:
        raise HTTPException(400, "Dimensions must be between 1 and 10000 pixels.")
    in_path = UPLOAD_DIR / f"{uuid.uuid4().hex}_{sanitize_filename(file.filename)}"
    out_path = in_path.with_suffix(".resized" + in_path.suffix)
    in_path.write_bytes(contents)
    result = ir.resize_image(str(in_path), str(out_path), width, height, keep_aspect=keep_aspect)
    bg.add_task(cleanup, [in_path, out_path])
    return FileResponse(str(out_path), media_type=result["mime"], filename=f"resized_{sanitize_filename(file.filename)}")

@app.get("/tool/palette", response_class=HTMLResponse)
async def palette_page(request: Request):
    return templates.TemplateResponse(request, "tool_palette.html", {"request": request, "year": datetime.now().year})

@app.post("/api/palette")
async def api_palette(file: UploadFile = File(...), num_colors: int = Form(5)):
    validate_file(file, {".png", ".jpg", ".jpeg", ".webp", ".gif"})
    contents = await file.read()
    if len(contents) > MAX_FILE_SIZE:
        raise HTTPException(413, f"File too large. Max {MAX_FILE_SIZE // (1024*1024)} MB.")
    if num_colors < 2 or num_colors > 20:
        raise HTTPException(400, "Number of colors must be between 2 and 20.")
    in_path = UPLOAD_DIR / f"{uuid.uuid4().hex}_{sanitize_filename(file.filename)}"
    try:
        in_path.write_bytes(contents)
        palette = ce.extract_palette(str(in_path), num_colors=num_colors)
        return JSONResponse({"palette": palette})
    finally:
        cleanup([in_path])

@app.get("/tool/qr", response_class=HTMLResponse)
async def qr_page(request: Request):
    return templates.TemplateResponse(request, "tool_qr.html", {"request": request, "year": datetime.now().year})

@app.post("/api/qr")
async def api_qr(data: str = Form(...), fill: str = Form("#000000"), back: str = Form("#ffffff"), bg: BackgroundTasks = BackgroundTasks()):
    if not data or len(data) > 2000:
        raise HTTPException(400, "Data must be between 1 and 2000 characters.")
    out_path = UPLOAD_DIR / f"qr_{uuid.uuid4().hex}.png"
    qg.generate_qr(data, str(out_path), fill_color=fill, back_color=back)
    bg.add_task(cleanup, [out_path])
    return FileResponse(str(out_path), media_type="image/png", filename="qrcode.png")

@app.get("/tool/text", response_class=HTMLResponse)
async def text_page(request: Request):
    return templates.TemplateResponse(request, "tool_text.html", {"request": request, "year": datetime.now().year})

@app.post("/api/text")
async def api_text(text: str = Form(...)):
    if len(text) > 100_000:
        raise HTTPException(400, "Text too long. Max 100,000 characters.")
    result = tt.analyze_text(text)
    return JSONResponse(result)

@app.exception_handler(413)
async def too_large(request, exc):
    return JSONResponse({"error": "File too large. Maximum upload size is 50 MB."}, status_code=413)
@app.exception_handler(400)
async def bad_request(request, exc):
    return JSONResponse({"error": str(exc.detail)}, status_code=400)
@app.exception_handler(500)
async def server_error(request, exc):
    return JSONResponse({"error": "Internal server error. Please try again."}, status_code=500)
