from typing import Type, Callable
from loguru import logger

from pydantic import BaseModel

from bb_integrations_lib.gravitate.rita_api import GravitateRitaAPI
from bb_integrations_lib.gravitate.sd_api import GravitateSDAPI
from bb_integrations_lib.pipelines.steps.exporting.bbd_order_exports.bbd_export_order_sftp_step import BBDExportOrderSFTPStep
from bb_integrations_lib.pipelines.steps.exporting.bbd_order_exports.bbd_get_orders_to_export_step import BBDGetOrdersToExportStep
from bb_integrations_lib.pipelines.steps.exporting.bbd_order_exports.bbd_finalize_export_statuses_step import BBDFinalizeExportStatuses
from bb_integrations_lib.pipelines.steps.exporting.bbd_order_exports.bbd_get_bols_and_drops_step import BBDGetBolsAndDrops
from bb_integrations_lib.pipelines.steps.exporting.bbd_order_exports.bbd_parse_order_step import BBDParseOrder
from bb_integrations_lib.pipelines.steps.exporting.bbd_order_exports.bbd_set_order_export_status import BBDSetExportOrderStatus
from bb_integrations_lib.pipelines.steps.exporting.save_rawdata_to_disk import SaveRawDataToDiskStep
from bb_integrations_lib.protocols.pipelines import JobPipeline, Parser, Step
from bb_integrations_lib.provider.ftp.client import FTPIntegrationClient
from bb_integrations_lib.secrets import SecretProvider
from bb_integrations_lib.shared.model import ERPStatus


class OrderExportConfig(BaseModel):
    file_base_name: str = "Gravitate-Order-Export"
    ftp_directory_path: str


class GenericOrderExportPipeline(JobPipeline):
    """
    Reusable order export pipeline that fetches unexported orders from S&D,
    parses them into CSV using a tenant-specific parser, and uploads via SFTP.

    Pipeline flow:
        1. get_orders_to_export  - Fetch unexported orders from S&D
        2. stage_orders          - Mark orders as 'staged' (skipped in test mode)
        3. get_bols_and_drops    - Fetch BOL/drop data, optionally run allocation matching
        4. parse_orders          - Parse order data into export rows using the provided parser
        5. sftp_export           - Upload CSV to SFTP (or save locally in test mode)
        6. archive (optional)    - Archive parsed output (e.g. to GCS). Skipped in test mode.
        final: collect_results   - Set export statuses (sent/errors/pending) regardless of success or failure
    """

    @classmethod
    async def create(
        cls,
        config_id: str,
        tenant_name: str,
        sd_client: GravitateSDAPI,
        rita_client: GravitateRitaAPI,
        ftp_client: FTPIntegrationClient,
        secret_provider: SecretProvider,
        export_config: OrderExportConfig,
        parser: Type[Parser],
        parser_kwargs: dict | None = None,
        include_allocation_matching: bool = True,
        send_reports: bool = True,
        test_order_numbers: list[str] | None = None,
        order_filter: Callable | None = None,
        archive_step: Step | None = None,
    ):
        is_test = test_order_numbers is not None
        logger.info(f"Running in Test(={is_test}). May save files locally")

        steps = [
            {
                "id": "get_orders_to_export",
                "parent_id": None,
                "step": BBDGetOrdersToExportStep(
                    sd_client=sd_client,
                    step_key="Order Numbers To Export",
                    test_order_numbers=test_order_numbers,
                )
            },
        ]

        if not is_test:
            steps.append({
                "id": "stage_orders",
                "parent_id": "get_orders_to_export",
                "step": BBDSetExportOrderStatus(
                    sd_client=sd_client,
                    step_key="Stage Orders",
                    global_status_override=ERPStatus.staged
                )
            })

        steps.extend([
            {
                "id": "get_bols_and_drops",
                "parent_id": "stage_orders" if not is_test else "get_orders_to_export",
                "step": BBDGetBolsAndDrops(
                    sd_client=sd_client,
                    include_allocation_matching=include_allocation_matching,
                    order_filter=order_filter,
                )
            },
            {
                "id": "parse_orders",
                "parent_id": "get_bols_and_drops",
                "step": BBDParseOrder(
                    step_key="Format Orders for Export",
                    file_base_name=export_config.file_base_name,
                    parser=parser,
                    parser_kwargs=parser_kwargs,
                )
            },
        ])


        if is_test:
            steps.append({
                "id": "sftp_export",
                "parent_id": "parse_orders",
                "step": SaveRawDataToDiskStep()
            })
        else:
            steps.append({
                "id": "sftp_export",
                "parent_id": "parse_orders",
                "step": BBDExportOrderSFTPStep(
                    sd_client=sd_client,
                    ftp_client=ftp_client,
                    ftp_destination_dir=export_config.ftp_directory_path,
                )
            })

        if archive_step and not is_test:
            steps.append({
                "id": "archive",
                "parent_id": "sftp_export",
                "alt_input": "parse_orders",
                "step": archive_step,
            })

        final = None
        if not is_test:
            final = {
                "id": "finalize_export_statuses",
                "step": BBDFinalizeExportStatuses(
                    sd_client=sd_client,
                    step_key="Order Export Results",
                ),
            }

        return cls(
            job_steps=steps,
            rita_client=rita_client,
            pipeline_name=f"{tenant_name} Order Export",
            pipeline_config_id=config_id,
            secret_provider=secret_provider,
            send_reports=send_reports,
            catch_step_errors=True,
            final_step=final,
        )
