import io
import os

import pytesseract
from PIL import Image, ImageFilter, ImageOps
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from starlette.concurrency import run_in_threadpool

from form941_parser import extract_text, parse_941
from w2_parser import parse_w2

# ─────────────────────────────────────────────────────────────────────────
# 1098-T parser.
# ─────────────────────────────────────────────────────────────────────────
from _1098_T import parse_1098t

# ─────────────────────────────────────────────────────────────────────────
# School Tax Bill parser.
# ─────────────────────────────────────────────────────────────────────────
from school_tax_bill_parser import parse_school_tax_bill

# ─────────────────────────────────────────────────────────────────────────
# SSA-1099 parser.
# ─────────────────────────────────────────────────────────────────────────
from ssa_1099_parser import parse_ssa1099

# ─────────────────────────────────────────────────────────────────────────
# 1099-NEC parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099_nec_2025_parser import parse_1099_nec_2025

# ─────────────────────────────────────────────────────────────────────────
# 1099-MISC parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099_misc_2025_parser import parse_1099_misc_2025

# ─────────────────────────────────────────────────────────────────────────
# 1099-K parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099_k_2025_parser import parse_1099_k_2025

# ─────────────────────────────────────────────────────────────────────────
# 1098 (Mortgage Interest) parser.
# ─────────────────────────────────────────────────────────────────────────
from _1098_parser import parse_1098

# ─────────────────────────────────────────────────────────────────────────
# 1099-INT (2024) parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099_int_2024_parser import parse_1099_int_2024

# ─────────────────────────────────────────────────────────────────────────
# 1099-R (2025) parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099_r_2025_parser import parse_1099_r_2025

# ─────────────────────────────────────────────────────────────────────────
# 1099-G (2021) parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099_g_2021_parser import parse_1099_g_2021

# ─────────────────────────────────────────────────────────────────────────
# 1099-C (Chase Bank Summary / standard IRS) parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099C_ChaseBank_Summary_parser import parse_1099_c_chase_summary

# ─────────────────────────────────────────────────────────────────────────
# 1099-C (2025 form) parser.
# ─────────────────────────────────────────────────────────────────────────
from _1099C_2025_parser import parse_1099_c_2025


app = FastAPI(
    title="OLPMS Python PDF Parser",
    version="2.8.0",
    root_path="/olpms-parser-api",
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:5173",
        "http://127.0.0.1:5173",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

TESSERACT_PATH = os.getenv(
    "TESSERACT_CMD",
    r"C:\Program Files\Tesseract-OCR\tesseract.exe",
)

if os.path.isfile(TESSERACT_PATH):
    pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH


MAX_PDF_SIZE = 25 * 1024 * 1024

ALLOWED_EXTENSIONS = (".pdf", ".png", ".jpg", ".jpeg")


# ─────────────────────────── File reader ───────────────────────────

async def read_pdf(file: UploadFile) -> tuple[str, bytes]:
    """Accept a PDF, PNG, or JPG upload and return (filename, bytes)."""
    filename = file.filename or "uploaded"

    if not filename.lower().endswith(ALLOWED_EXTENSIONS):
        raise HTTPException(
            status_code=400,
            detail="Please upload a PDF, PNG, or JPG file.",
        )

    data = await file.read()

    if not data:
        raise HTTPException(
            status_code=400,
            detail="The uploaded file is empty.",
        )

    if len(data) > MAX_PDF_SIZE:
        raise HTTPException(
            status_code=413,
            detail="File is too large. Maximum allowed size is 25 MB.",
        )

    return filename, data


# ─────────────────────────── Unified page extractor ───────────────────────────

def _ocr_image(img: Image.Image) -> str:
    """Run Tesseract on a single PIL image (two PSM fallbacks)."""
    text = pytesseract.image_to_string(img, lang="eng", config="--psm 6")
    if len(text.strip()) < 10:
        text = pytesseract.image_to_string(img, lang="eng", config="--psm 3")
    return text


def extract_pages(filename: str, data: bytes):
    """Return a list of page dicts, from either a PDF or an image.

    Each page dict has the same shape the parsers expect:
    { page, text, text_alt, width, height, source, image }
    """
    lower = (filename or "").lower()

    # PDFs → reuse the existing fitz-based extractor (returns full page dicts)
    if lower.endswith(".pdf"):
        return extract_text(data)

    # Images → PIL + pytesseract (two passes: plain and sharpened)
    img = Image.open(io.BytesIO(data)).convert("L")

    target_width = 2500
    scale = target_width / img.width
    if scale > 1.05:
        img = img.resize(
            (round(img.width * scale), round(img.height * scale)),
            Image.LANCZOS,
        )

    base = ImageOps.autocontrast(img)
    sharp = base.filter(ImageFilter.UnsharpMask(2, 150, 3))

    return [
        {
            "page": 1,
            "text": _ocr_image(base),
            "text_alt": _ocr_image(sharp),
            "width": img.width,
            "height": img.height,
            "source": "ocr",
            "image": img,
        }
    ]


# ─────────────────────────── Health ───────────────────────────

@app.get("/health")
def health():
    return {
        "status": "ok",
        "service": "olpms-python-pdf-parser",
    }


# ─────────────────────────── Form 941 ───────────────────────────

@app.post("/parse-941")
async def parse_941_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_941, pages, filename)

        if isinstance(result, dict):
            result["filename"] = filename
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this Form 941 file: {exc}",
        ) from exc


# ─────────────────────────── Form W-2 ───────────────────────────

@app.post("/parse-w2")
async def parse_w2_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_w2, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this W-2 file: {exc}",
        ) from exc


# ─────────────────────────── Form 1098-T ───────────────────────────

@app.post("/parse-1098-t")
async def parse_1098t_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1098t, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this Form 1098-T file: {exc}",
        ) from exc


# ─────────────────────────── School Tax Bill ───────────────────────────

@app.post("/parse-school-tax-bill")
async def parse_school_tax_bill_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_school_tax_bill, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this School Tax Bill file: {exc}",
        ) from exc


# ─────────────────────────── SSA-1099 ───────────────────────────

@app.post("/parse-ssa1099")
async def parse_ssa1099_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_ssa1099, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this SSA-1099 file: {exc}",
        ) from exc


# ─────────────────────────── 1099-NEC (2025) ───────────────────────────

@app.post("/parse-1099-nec-2025")
async def parse_1099_nec_2025_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_nec_2025, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-NEC file: {exc}",
        ) from exc


# ─────────────────────────── 1099-MISC (2025) ───────────────────────────

@app.post("/parse-1099-misc-2025")
async def parse_1099_misc_2025_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_misc_2025, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-MISC file: {exc}",
        ) from exc


# ─────────────────────────── 1099-K (2025) ───────────────────────────

@app.post("/parse-1099-k-2025")
async def parse_1099_k_2025_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_k_2025, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-K file: {exc}",
        ) from exc


# ─────────────────────────── 1098 (Mortgage Interest) ───────────────────────────

@app.post("/parse-1098")
async def parse_1098_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1098, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this Form 1098 file: {exc}",
        ) from exc


# ─────────────────────────── 1099-INT (2024) ───────────────────────────

@app.post("/parse-1099-int-2024")
async def parse_1099_int_2024_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_int_2024, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-INT file: {exc}",
        ) from exc


# ─────────────────────────── 1099-R (2025) ───────────────────────────

@app.post("/parse-1099-r-2025")
async def parse_1099_r_2025_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_r_2025, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-R file: {exc}",
        ) from exc


# ─────────────────────────── 1099-G (2021) ───────────────────────────

@app.post("/parse-1099-g-2021")
async def parse_1099_g_2021_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_g_2021, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-G file: {exc}",
        ) from exc


# ─────────────────────────── 1099-C (Chase / IRS) ───────────────────────────

@app.post("/parse-1099-c-chase-summary")
async def parse_1099_c_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_c_chase_summary, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-C file: {exc}",
        ) from exc


# ─────────────────────────── 1099-C (2025 form) ───────────────────────────

@app.post("/parse-1099-c-2025")
async def parse_1099_c_2025_endpoint(file: UploadFile = File(...)):
    filename, data = await read_pdf(file)

    try:
        pages = await run_in_threadpool(extract_pages, filename, data)

        if not pages:
            raise HTTPException(
                status_code=422,
                detail="The file contains no readable pages.",
            )

        result = await run_in_threadpool(parse_1099_c_2025, pages, filename)

        if isinstance(result, dict):
            result.setdefault("filename", filename)
            return result

        return {"filename": filename, "data": result}

    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=422,
            detail=f"Unable to parse this 1099-C (2025) file: {exc}",
        ) from exc