import pdfplumber
import json
import re
import sys
import os
from datetime import datetime


OUTPUT_SUFFIX = "_order.json"
OUTPUT_MODES = (
    "jsn",
    "output",
    "jsn_output",
)


# ============================================================
# BASIC FUNCTIONS
# ============================================================

def clean_text(value):

    if value is None:
        return ""

    value = str(value)

    value = value.replace("\r", " ")
    value = value.replace("\n", " ")

    value = re.sub(
        r"\s+",
        " ",
        value
    )

    return value.strip()


def clean_number(value):

    if value is None:
        return ""

    value = str(value)

    value = re.sub(
        r"\s+",
        "",
        value
    )

    value = value.replace(
        ",",
        ""
    )

    return value


def to_number(value):

    value = clean_number(value)

    if not value:
        return 0

    value = value.replace(
        "%",
        ""
    )

    value = re.sub(
        r"[^\d.\-]",
        "",
        value
    )

    if not value:
        return 0

    try:

        number = float(value)

        if number.is_integer():

            return int(number)

        return number

    except Exception:

        return 0


# ============================================================
# DATE FORMAT
# ============================================================

def format_date(value):

    if not value:
        return ""

    value = clean_text(value)

    # Remove time
    value = re.sub(
        r"\s+\d{1,2}:\d{2}(?::\d{2})?(?:\s*[AP]M)?$",
        "",
        value,
        flags=re.IGNORECASE
    ).strip()

    # --------------------------------------------------------
    # YYYY-MM-DD
    # YYYY/MM/DD
    # --------------------------------------------------------

    match = re.search(
        r"\b(\d{4})[-/](\d{1,2})[-/](\d{1,2})\b",
        value
    )

    if match:

        try:

            dt = datetime(
                int(match.group(1)),
                int(match.group(2)),
                int(match.group(3))
            )

            return dt.strftime(
                "%d/%m/%Y"
            )

        except Exception:
            pass

    # --------------------------------------------------------
    # DD-MM-YYYY
    # DD/MM/YYYY
    # --------------------------------------------------------

    match = re.search(
        r"\b(\d{1,2})[-/](\d{1,2})[-/](\d{4})\b",
        value
    )

    if match:

        try:

            dt = datetime(
                int(match.group(3)),
                int(match.group(2)),
                int(match.group(1))
            )

            return dt.strftime(
                "%d/%m/%Y"
            )

        except Exception:
            pass

    # --------------------------------------------------------
    # Aug 27, 2026
    # August 27, 2026
    # --------------------------------------------------------

    match = re.search(
        r"\b([A-Za-z]{3,9})\s+"
        r"(\d{1,2}),?\s+"
        r"(\d{4})\b",
        value
    )

    if match:

        date_text = "{} {} {}".format(
            match.group(1),
            match.group(2),
            match.group(3)
        )

        for fmt in (
            "%b %d %Y",
            "%B %d %Y"
        ):

            try:

                dt = datetime.strptime(
                    date_text,
                    fmt
                )

                return dt.strftime(
                    "%d/%m/%Y"
                )

            except Exception:
                pass

    # --------------------------------------------------------
    # 27 Aug 2026
    # 27 August 2026
    # --------------------------------------------------------

    match = re.search(
        r"\b(\d{1,2})\s+"
        r"([A-Za-z]{3,9})\s+"
        r"(\d{4})\b",
        value
    )

    if match:

        date_text = "{} {} {}".format(
            match.group(1),
            match.group(2),
            match.group(3)
        )

        for fmt in (
            "%d %b %Y",
            "%d %B %Y"
        ):

            try:

                dt = datetime.strptime(
                    date_text,
                    fmt
                )

                return dt.strftime(
                    "%d/%m/%Y"
                )

            except Exception:
                pass

    return ""


# ============================================================
# GET COMPLETE PDF TEXT
# ============================================================

def get_pdf_text(pdf_file):

    pages_text = []

    with pdfplumber.open(
        pdf_file
    ) as pdf:

        for page in pdf.pages:

            text = page.extract_text(
                x_tolerance=2,
                y_tolerance=3
            )

            if text:

                pages_text.append(
                    text
                )

    return "\n".join(
        pages_text
    )


# ============================================================
# GET FIRST PAGE TEXT
# ============================================================

def get_first_page_text(pdf_file):

    with pdfplumber.open(
        pdf_file
    ) as pdf:

        if not pdf.pages:

            return ""

        text = pdf.pages[0].extract_text(
            x_tolerance=2,
            y_tolerance=3
        )

        return text or ""


# ============================================================
# PO NUMBER
# ============================================================

def get_po_number(text):

    patterns = [

        r"\bPO\s*No\s*[:\-]?\s*"
        r"([A-Z0-9][A-Z0-9\-\/]*)",

        r"\bPO\s*Number\s*[:\-]?\s*"
        r"([A-Z0-9][A-Z0-9\-\/]*)",

        r"\bP\.O\.?\s*No\s*[:\-]?\s*"
        r"([A-Z0-9][A-Z0-9\-\/]*)",

        r"\bPO\s*#\s*[:\-]?\s*"
        r"([A-Z0-9][A-Z0-9\-\/]*)"
    ]

    for pattern in patterns:

        match = re.search(
            pattern,
            text,
            re.IGNORECASE
        )

        if match:

            return clean_text(
                match.group(1)
            )

    return ""


# ============================================================
# PO DATE
# ============================================================

def get_po_date(text):

    # --------------------------------------------------------
    # Line based
    # --------------------------------------------------------

    for line in text.splitlines():

        line = clean_text(
            line
        )

        if not line:

            continue

        # IMPORTANT:
        # Must start with PO Date.
        # This prevents PO Release Date matching.

        match = re.match(
            r"^PO\s+Date\s*:?\s*(.*)$",
            line,
            re.IGNORECASE
        )

        if match:

            value = clean_text(
                match.group(1)
            )

            result = format_date(
                value
            )

            if result:

                return result

    # --------------------------------------------------------
    # Global fallback
    # --------------------------------------------------------

    match = re.search(

        r"\bPO\s+Date\s*:?\s*"
        r"("
        r"\d{4}[-/]\d{1,2}[-/]\d{1,2}"
        r"|"
        r"\d{1,2}[-/]\d{1,2}[-/]\d{4}"
        r"|"
        r"[A-Za-z]{3,9}\s+\d{1,2},?\s+\d{4}"
        r"|"
        r"\d{1,2}\s+[A-Za-z]{3,9}\s+\d{4}"
        r")",

        text,

        re.IGNORECASE
    )

    if match:

        return format_date(
            match.group(1)
        )

    return ""


# ============================================================
# DELIVERY DATE
# ============================================================

def get_delivery_date(text):

    for line in text.splitlines():

        line = clean_text(
            line
        )

        if not line:

            continue

        match = re.match(
            r"^Expected\s+Delivery\s+Date\s*:?\s*(.*)$",
            line,
            re.IGNORECASE
        )

        if match:

            value = clean_text(
                match.group(1)
            )

            result = format_date(
                value
            )

            if result:

                return result

    # Fallback

    match = re.search(

        r"Expected\s+Delivery\s+Date\s*:?\s*"
        r"("
        r"\d{4}[-/]\d{1,2}[-/]\d{1,2}"
        r"|"
        r"\d{1,2}[-/]\d{1,2}[-/]\d{4}"
        r"|"
        r"[A-Za-z]{3,9}\s+\d{1,2},?\s+\d{4}"
        r"|"
        r"\d{1,2}\s+[A-Za-z]{3,9}\s+\d{4}"
        r")",

        text,

        re.IGNORECASE
    )

    if match:

        return format_date(
            match.group(1)
        )

    return ""


# ============================================================
# ADDRESS EXTRACTION - TEXT BASED
#
# Billing Address / Shipping Address start on one heading line.
# Read lines until CGST, then take index - 1 as the last
# address line. Do not stop at PAN / Contact / GSTIN.
# Two-column layouts are split using word x positions.
# ============================================================

def is_table_header_line(line):

    if re.search(
        r"\b(GSTIN|PAN|Contact|Pod\s*Name)\b",
        line,
        re.IGNORECASE
    ):

        return False

    if re.search(
        r"(LIMITED|Village|Panchayat|SCOOTSY|@)",
        line,
        re.IGNORECASE
    ):

        return False

    return bool(
        re.search(
            r"\b(Unit|Taxabl|Additi|Item|HSN|"
            r"Description|Qty|MRP|Base|Code|"
            r"S\.?\s*No|#)\b",
            line,
            re.IGNORECASE
        )
    )


def split_two_column_addresses(
    pdf_file,
    heading_index,
    end_index
):

    with pdfplumber.open(
        pdf_file
    ) as pdf:

        if not pdf.pages:

            return "", ""

        page = pdf.pages[0]

        words = page.extract_words(
            x_tolerance=2,
            y_tolerance=3
        )

    if not words:

        return "", ""

    heading_top = None

    for word in words:

        if re.fullmatch(
            r"billing",
            word["text"],
            re.IGNORECASE
        ):

            heading_top = word["top"]

            break

    rows = {}

    for word in words:

        key = round(
            word["top"],
            1
        )

        rows.setdefault(
            key,
            []
        ).append(
            word
        )

    split_x = None
    best_gap = 0

    for key in sorted(rows):

        if heading_top is not None and key <= round(heading_top, 1):

            continue

        row_words = sorted(
            rows[key],
            key=lambda item: item["x0"]
        )

        joined = " ".join(
            item["text"]
            for item in row_words
        )

        if is_table_header_line(
            joined
        ):

            continue

        if re.search(
            r"\bCGST\b",
            joined,
            re.IGNORECASE
        ):

            break

        for i in range(len(row_words) - 1):

            gap = (
                row_words[i + 1]["x0"]
                - row_words[i]["x1"]
            )

            if gap > best_gap:

                best_gap = gap

                split_x = row_words[i + 1]["x0"]

        if best_gap > 40:

            break

    if split_x is None:

        split_x = page.width / 2

    bill_parts = []
    ship_parts = []
    taken = 0
    max_rows = max(
        end_index - heading_index,
        0
    )

    for key in sorted(rows):

        row_words = sorted(
            rows[key],
            key=lambda item: item["x0"]
        )

        joined = " ".join(
            item["text"]
            for item in row_words
        )

        if heading_top is not None and key <= round(heading_top, 1):

            continue

        if is_table_header_line(
            joined
        ):

            continue

        if re.search(
            r"\bCGST\b",
            joined,
            re.IGNORECASE
        ):

            break

        if taken >= max_rows:

            break

        left = []
        right = []

        for item in row_words:

            if item["x0"] < split_x:

                left.append(
                    item["text"]
                )

            else:

                right.append(
                    item["text"]
                )

        if left:

            bill_parts.append(
                " ".join(left)
            )

        if right:

            ship_parts.append(
                " ".join(right)
            )

        taken += 1

    return (
        clean_text(
            " ".join(bill_parts)
        ),
        clean_text(
            " ".join(ship_parts)
        )
    )


def get_addresses_from_first_page(pdf_file):

    with pdfplumber.open(
        pdf_file
    ) as pdf:

        if not pdf.pages:

            return "", ""

        tables = pdf.pages[0].extract_tables({
            "vertical_strategy": "lines",
            "horizontal_strategy": "lines",
            "snap_tolerance": 3,
            "join_tolerance": 3,
            "intersection_tolerance": 3,
            "edge_min_length": 3
        })

    for table in tables:

        address_start = -1
        shipping_column = -1

        for row_index, row in enumerate(table):

            row = row or []

            for column_index, value in enumerate(row):

                value = clean_text(
                    value
                )

                if re.search(
                    r"\bBilling\s+Address\b",
                    value,
                    re.IGNORECASE
                ):

                    address_start = row_index

                if re.search(
                    r"\bShipping\s+Address\b",
                    value,
                    re.IGNORECASE
                ):

                    shipping_column = column_index

            if address_start >= 0 and shipping_column >= 0:

                break

        if address_start < 0 or shipping_column < 0:

            continue

        billing_parts = []
        shipping_parts = []

        for row in table[address_start + 1:]:

            row = row or []

            joined = " ".join(
                clean_text(value)
                for value in row
            )

            if re.search(
                r"\bSr\.?\s+Material\s+Code\b",
                joined,
                re.IGNORECASE
            ):

                break

            billing_text = clean_text(
                " ".join(
                    clean_text(value)
                    for value in row[:shipping_column]
                    if clean_text(value)
                )
            )

            shipping_text = clean_text(
                " ".join(
                    clean_text(value)
                    for value in row[shipping_column:]
                    if clean_text(value)
                )
            )

            if billing_text:

                billing_parts.append(
                    billing_text
                )

            if shipping_text:

                shipping_parts.append(
                    shipping_text
                )

        return (
            clean_text(
                " ".join(billing_parts)
            ),
            clean_text(
                " ".join(shipping_parts)
            )
        )

    return "", ""


# ============================================================
# ZEPTO HEADER TOTALS
#
# Total Taxable Amount (INR) 63320.69
# Total Tax (INR) 3784.75
# Grand Total Amount (INR) 67105.44
# ============================================================

def get_header_totals(text):

    result = {

        "po_total_amount":
            0,

        "po_gst_compensation_cess":
            0,

        "po_gst_additional_cess":
            0,

        "po_total_tax":
            0,

        "pdf_grand_total":
            0
    }

    # --------------------------------------------------------
    # Normalize whitespace
    # --------------------------------------------------------

    text = re.sub(
        r"\s+",
        " ",
        text
    )

    # --------------------------------------------------------
    # Total Taxable Amount
    # --------------------------------------------------------

    match = re.search(

        r"Total\s+Taxable\s+Amount"
        r"\s*\(\s*INR\s*\)"
        r"\s*:?\s*"
        r"([\d,]+(?:\.\d+)?)",

        text,

        re.IGNORECASE
    )

    if match:

        result[
            "po_total_amount"
        ] = to_number(
            match.group(1)
        )

    # --------------------------------------------------------
    # GST Compensation Cess
    # --------------------------------------------------------

    match = re.search(

        r"GST\s+Compensation\s+Cess"
        r"\s*:?\s*"
        r"([\d,]+(?:\.\d+)?)",

        text,

        re.IGNORECASE
    )

    if match:

        result[
            "po_gst_compensation_cess"
        ] = to_number(
            match.group(1)
        )

    # --------------------------------------------------------
    # GST Additional Cess
    # --------------------------------------------------------

    match = re.search(

        r"GST\s+Additional\s+Cess"
        r"\s*:?\s*"
        r"([\d,]+(?:\.\d+)?)",

        text,

        re.IGNORECASE
    )

    if match:

        result[
            "po_gst_additional_cess"
        ] = to_number(
            match.group(1)
        )

    # --------------------------------------------------------
    # Total Tax
    # --------------------------------------------------------

    match = re.search(

        r"Total\s+Tax"
        r"\s*\(\s*INR\s*\)"
        r"\s*:?\s*"
        r"([\d,]+(?:\.\d+)?)",

        text,

        re.IGNORECASE
    )

    if match:

        result[
            "po_total_tax"
        ] = to_number(
            match.group(1)
        )

    # --------------------------------------------------------
    # Grand Total Amount
    # --------------------------------------------------------

    match = re.search(

        r"Grand\s+Total\s+Amount"
        r"\s*\(\s*INR\s*\)"
        r"\s*:?\s*"
        r"([\d,]+(?:\.\d+)?)",

        text,

        re.IGNORECASE
    )

    if match:

        result[
            "pdf_grand_total"
        ] = to_number(
            match.group(1)
        )

    return result


# ============================================================
# ITEM NUMBER
# ============================================================

def normalize_item_no(value):

    if value is None:

        return ""

    value = str(value)

    value = re.sub(
        r"\s+",
        "",
        value
    )

    return value


def is_item_number(value):

    value = normalize_item_no(
        value
    )

    return bool(
        re.fullmatch(
            r"\d{1,3}",
            value
        )
    )


# ============================================================
# TABLE EXTRACTION
# ============================================================

def get_tables(pdf_file):

    all_rows = []

    with pdfplumber.open(
        pdf_file
    ) as pdf:

        print(
            "Total PDF pages:",
            len(pdf.pages),
            file=sys.stderr
        )

        for page_no, page in enumerate(
            pdf.pages,
            start=1
        ):

            print(
                "Reading table page:",
                page_no,
                file=sys.stderr
            )

            tables = page.extract_tables({

                "vertical_strategy":
                    "lines",

                "horizontal_strategy":
                    "lines",

                "snap_tolerance":
                    3,

                "join_tolerance":
                    3,

                "intersection_tolerance":
                    3,

                "edge_min_length":
                    3
            })

            if not tables:

                continue

            for table in tables:

                for row in table:

                    if not row:

                        continue

                    row = [
                        clean_text(x)
                        for x in row
                    ]

                    if not any(row):

                        continue

                    joined = " ".join(
                        row
                    ).lower()

                    # Ignore headers
                    if (
                        "item code" in joined
                        or "item desc" in joined
                        or "taxable value" in joined
                        or "unit base cost" in joined
                        or "hsn code" in joined
                    ):

                        continue

                    # ------------------------------------------------
                    # ITEM
                    # ------------------------------------------------

                    if is_item_number(
                        row[0]
                    ):

                        all_rows.append(
                            row
                        )

                        continue

                    # ------------------------------------------------
                    # Continuation
                    # ------------------------------------------------

                    if (
                        all_rows
                        and not row[0]
                        and len(row) > 2
                    ):

                        description = clean_text(
                            row[2]
                        )

                        if description:

                            old = clean_text(
                                all_rows[-1][2]
                            )

                            all_rows[-1][2] = (
                                old
                                + " "
                                + description
                            )

    return all_rows


# ============================================================
# CREATE ITEM ARRAY
# ============================================================

def create_final_po_items(rows):

    final_po_items = {}

    for row in rows:

        if len(row) < 8:

            continue

        # ----------------------------------------------------
        # SL NO
        # ----------------------------------------------------

        sl_no = normalize_item_no(
            row[0]
        )

        if not is_item_number(
            sl_no
        ):

            continue

        # ----------------------------------------------------
        # SKU
        # ----------------------------------------------------

        sku_code = ""

        if len(row) > 1:

            sku_code = clean_text(
                row[1]
            ).upper()

        # ----------------------------------------------------
        # DESCRIPTION
        # ----------------------------------------------------

        description = ""

        if len(row) > 2:

            description = clean_text(
                row[2]
            ).upper()

        # Zepto columns:
        # 0 Sr., 1 Material Code, 2 Description, 4 SKU UUID,
        # 5 HSN, 6 EAN, 7 Quantity, 8 MRP, 10 Unit Cost,
        # 11 Taxable Value, 12 CGST, 16 SGST, 18 IGST.
        hsn = ""

        if len(row) > 5:

            hsn = clean_text(
                row[5]
            )

        # ----------------------------------------------------
        # QTY
        # ----------------------------------------------------

        qty = 0

        if len(row) > 7:

            qty = to_number(
                row[7]
            )

        # ----------------------------------------------------
        # MRP
        # ----------------------------------------------------

        mrp = 0

        if len(row) > 8:

            mrp = to_number(
                row[8]
            )

        # ----------------------------------------------------
        # RATE
        # ----------------------------------------------------

        rate = 0

        if len(row) > 10:

            rate = to_number(
                row[10]
            )

        # ----------------------------------------------------
        # TAXABLE VALUE
        # ----------------------------------------------------

        taxable_value = 0

        if len(row) > 11:

            taxable_value = to_number(
                row[11]
            )

        # ----------------------------------------------------
        # TAX RATE
        # ----------------------------------------------------

        cgst_rate = 0
        sgst_rate = 0
        igst_rate = 0

        if len(row) > 12:

            cgst_rate = to_number(
                row[12]
            )

        if len(row) > 16:

            sgst_rate = to_number(
                row[16]
            )

        if len(row) > 18:

            igst_rate = to_number(
                row[18]
            )

        if cgst_rate or sgst_rate:

            po_tax_rate = (
                cgst_rate
                + sgst_rate
            )

        else:

            po_tax_rate = igst_rate

        # ----------------------------------------------------
        # TOTAL
        # ----------------------------------------------------

        total = 0

        if row:

            total = to_number(
                row[-1]
            )

        # ----------------------------------------------------
        # ITEM
        # ----------------------------------------------------

        final_po_items[sl_no] = {

            "po_item_description":
                description,

            "po_item_sku_code":
                sku_code,

            "po_item_hsn_code":
                hsn,

            "qty":
                qty,

            "po_item_mrp":
                mrp,

            "rate":
                rate,

            "taxable_value":
                taxable_value,

            "po_tax_rate":
                po_tax_rate,

            "tot_amt":
                total,

            "po_item_barcode":
                clean_text(
                    row[6]
                )
                if len(row) > 6
                else ""
        }

    return final_po_items


# ============================================================
# ORDER QTY
# ============================================================

def get_order_qty(items):

    order_qty = 0

    for item in items.values():

        order_qty += to_number(
            item.get(
                "qty",
                0
            )
        )

    return order_qty


# ============================================================
# ORDER VALUE
# ============================================================

def get_order_value(items):

    total_po_value = 0

    for item in items.values():

        total_po_value += to_number(
            item.get(
                "tot_amt",
                0
            )
        )

    return round(
        total_po_value,
        2
    )


# ============================================================
# CGST / SGST
# ============================================================

def check_cgst_sgst(text):

    has_cgst = re.search(
        r"\bCGST\b",
        text,
        re.IGNORECASE
    )

    has_sgst = re.search(
        r"\bSGST\b",
        text,
        re.IGNORECASE
    )

    if has_cgst and has_sgst:

        return 1

    return 0


# ============================================================
# FINAL DATA
# ============================================================

def create_final_data(
    pdf_file,
    items
):

    text = get_pdf_text(
        pdf_file
    )

    # --------------------------------------------------------
    # HEADER
    # --------------------------------------------------------

    po_number = get_po_number(
        text
    )

    po_date = get_po_date(
        text
    )

    delivery_date = get_delivery_date(
        text
    )

    ship_date = delivery_date

    # --------------------------------------------------------
    # ADDRESSES
    # --------------------------------------------------------

    (
        bill_to_address,
        ship_to_address
    ) = get_addresses_from_first_page(
        pdf_file
    )

    # --------------------------------------------------------
    # TOTALS
    # --------------------------------------------------------

    header_totals = get_header_totals(
        text
    )

    # --------------------------------------------------------
    # ORDER QTY
    # --------------------------------------------------------

    order_qty = get_order_qty(
        items
    )

    # --------------------------------------------------------
    # ORDER VALUE
    # --------------------------------------------------------

    order_value = get_order_value(
        items
    )

    # --------------------------------------------------------
    # FINAL ARRAY
    # --------------------------------------------------------

    final_data = {

        "po_number":
            po_number,

        "po_date":
            po_date,

        "delivery_date":
            delivery_date,

        "ship_date":
            ship_date,

        "bill_to_address":
            bill_to_address,

        "ship_to_address":
            ship_to_address,

        "po_total_amount":
            header_totals[
                "po_total_amount"
            ],

        "po_total_tax":
            header_totals[
                "po_total_tax"
            ],

        "pdf_grand_total":
            header_totals[
                "pdf_grand_total"
            ],

        "order_value":
            order_value,

        "order_qty":
            order_qty,

        "items":
            items,
    }

    return final_data


# ============================================================
# SAVE
# ============================================================

def save_json(
    data,
    output_file
):

    with open(
        output_file,
        "w",
        encoding="utf-8"
    ) as f:

        json.dump(
            data,
            f,
            indent=4,
            ensure_ascii=False
        )


# ============================================================
# PARSE PDF
# ============================================================

def normalize_output_mode(value):

    if not value:

        return "output"

    value = clean_text(
        value
    ).lower()

    if value in (
        "json",
        "--json",
        "jsn",
    ):

        return "jsn"

    if value in OUTPUT_MODES:

        return value

    raise ValueError(
        "Invalid output mode: {}. Use jsn, output, or jsn_output.".format(
            value
        )
    )


def get_output_file_path(pdf_file):

    directory = os.path.dirname(
        os.path.abspath(
            pdf_file
        )
    )

    filename = os.path.splitext(
        os.path.basename(
            pdf_file
        )
    )[0]

    return os.path.join(
        directory,
        filename + OUTPUT_SUFFIX
    )


def parse_pdf(
    pdf_file,
    save_output=True
):

    rows = get_tables(
        pdf_file
    )

    items = create_final_po_items(
        rows
    )

    final_data = create_final_data(
        pdf_file,
        items
    )

    output_file = get_output_file_path(
        pdf_file
    )

    if save_output:

        save_json(
            final_data,
            output_file
        )

    else:

        output_file = ""

    return (
        final_data,
        output_file,
        items,
        rows
    )


# ============================================================
# MAIN
# ============================================================

def main():

    args = sys.argv[1:]
    mode = "output"

    if "--json" in args:

        mode = "jsn"

        args.remove(
            "--json"
        )

    if len(args) < 1:

        print(
            "Usage:"
        )

        print(
            "python zepto_parser.py sample.pdf [jsn|output|jsn_output]"
        )

        print(
            "  jsn         : return JSON only, do not save file"
        )

        print(
            "  output      : save JSON file and print summary"
        )

        print(
            "  jsn_output  : save JSON file and return JSON"
        )

        sys.exit(1)

    pdf_file = args[0]

    if len(args) >= 2:

        try:

            mode = normalize_output_mode(
                args[1]
            )

        except ValueError as e:

            message = str(e)

            print(
                json.dumps(
                    {
                        "error": message
                    }
                )
                if mode == "jsn"
                else "ERROR: {}".format(message),
                file=sys.stdout
                if mode == "jsn"
                else sys.stderr
            )

            sys.exit(1)

    emit_json = mode in (
        "jsn",
        "jsn_output",
    )

    save_output = mode in (
        "output",
        "jsn_output",
    )

    if not os.path.isfile(
        pdf_file
    ):

        message = "PDF not found: {}".format(
            pdf_file
        )

        if emit_json:

            print(
                json.dumps(
                    {
                        "error": message
                    }
                )
            )

        else:

            print(
                "ERROR:",
                message
            )

        sys.exit(1)

    try:

        (
            final_data,
            output_file,
            items,
            rows
        ) = parse_pdf(
            pdf_file,
            save_output=save_output
        )

        if emit_json:

            print(
                json.dumps(
                    final_data,
                    ensure_ascii=False
                )
            )

            if mode == "jsn":

                return

        if mode == "jsn_output":

            return

        print()
        print(
            "Rows found:",
            len(rows)
        )

        print(
            "Items found:",
            len(items)
        )
        print("=" * 100)
        print("FINAL RESULT")
        print("=" * 100)

        print(
            "PO Number             :",
            final_data["po_number"]
        )

        print(
            "PO Date               :",
            final_data["po_date"]
        )

        print(
            "Delivery Date         :",
            final_data["delivery_date"]
        )

        print(
            "Ship Date             :",
            final_data["ship_date"]
        )

        print(
            "Bill To               :",
            final_data["bill_to_address"]
        )

        print(
            "Ship To               :",
            final_data["ship_to_address"]
        )

        print(
            "Total Amount          :",
            final_data["po_total_amount"]
        )

        print(
            "Total Tax             :",
            final_data[
                "po_total_tax"
            ]
        )

        print(
            "Grand Total           :",
            final_data[
                "pdf_grand_total"
            ]
        )

        print(
            "Order Value           :",
            final_data[
                "order_value"
            ]
        )

        print(
            "Order Qty             :",
            final_data[
                "order_qty"
            ]
        )

        print(
            "Output File           :",
            output_file
        )

        print("=" * 100)

        # ----------------------------------------------------
        # ITEMS
        # ----------------------------------------------------

        print()
        print(
            "ITEM DETAILS"
        )

        print(
            "-" * 180
        )

        for sl_no, item in items.items():

            print(
                "{} | SKU={} | HSN={} | QTY={} | MRP={} | RATE={} | TAXABLE={} | TAX={} | TOTAL={}".format(

                    sl_no,

                    item[
                        "po_item_sku_code"
                    ],

                    item[
                        "po_item_hsn_code"
                    ],

                    item[
                        "qty"
                    ],

                    item[
                        "po_item_mrp"
                    ],

                    item[
                        "rate"
                    ],

                    item[
                        "taxable_value"
                    ],

                    item[
                        "po_tax_rate"
                    ],

                    item[
                        "tot_amt"
                    ]
                )
            )

    except Exception as e:

        message = str(e)

        if emit_json:

            print(
                json.dumps(
                    {
                        "error": message
                    }
                )
            )

        else:

            print()
            print(
                "ERROR:",
                message
            )

        sys.exit(1)


# ============================================================
# START
# ============================================================

if __name__ == "__main__":

    main()