from fastapi import FastAPI, HTTPException, File, Form, UploadFile, Request, Query,BackgroundTasks
from pydantic import BaseModel, Field
from enum import Enum
from typing import List, Optional, Union, Literal
from datetime import datetime
from scripts.Overdue_Reminder import overdue_main
from scripts.ptp_file_processing import process_ptp_excel
from scripts.pincode_master_processing import process_pincode_master_excel
from scripts.blacksoil_lms.co_lending_bulk_allocation import co_lending_bulk_allocation_function
from scripts.blacksoil_lms.co_lending_parked_cron import process_parked_funds
from scripts.merchant_collection_report import generate_merchant_collection_report
from scripts.blacksoil_lms.blacksoil_accrued_revenue import calculate_accrued_revenue
from scripts.accrued_revenue_eir import generate_accrued_revenue_report
from scripts.generate_collection_report import generate_collection_report_job
import traceback, os, re, json

from scripts import (
    rps_function,
    payment_function,
    vivriti_cam_function,
    utkarsh_cam_function,
    pre_part_payment_function,
    overdue_function,
    bounce_penal_function,
    foreclosure_breakup_function,
    subvention_rps_function,
    settlement_reconcile_function,
    foreclosure_allocation_function,
    dpd_report_function,
    loan_metrics_calculation,
    homeville_rps_update_function,
    pre_part_payment_v2_function,
    blacksoil_rps_function,
    blacksoil_payments_function,
    co_lending_bulk_rps_function
)

app = FastAPI()


##### rps_generation.py #####
class RPSgeneration(BaseModel):
    loan_principal: int = Field(...,
                                description="Net loan amount = Approved amount - upfront from customer(down payment).")
    annualised_interest: float = Field(...,
                                       description="emi_calculation.annualised_interest - used for calculating emi.")
    annualised_irr: float = Field(..., description="emi_calculation.annualised_irr - used for calculating bpi.")
    remaining_emi: int = Field(..., description="net_tenure. emi_calculation.remaining_emi")
    disbursal_date_str: str = Field(..., description="Loan disbursal date in DD-MM-YYYY format.")
    first_emi_date_str: str = Field(..., description="First EMI date in DD-MM-YYYY format.")
    morat_months: int = Field(..., description="Number of months under moratorium (if any, else 0).")
    morat_start_date_str: str = Field(..., description="Start date of moratorium in DD-MM-YYYY format.")

    model_config = {
        "json_schema_extra": {
            "example": {
                "loan_principal": 189600,
                "annualised_interest": 16.58,
                "annualised_irr": 16.58,
                "remaining_emi": 36,
                "disbursal_date_str": "01-10-2025",
                "first_emi_date_str": "05-11-2025",
                "morat_months": 0,
                "morat_start_date_str": "05-10-2025"
            }
        }
    }


@app.post("/api/5th_emi_date")
async def rps_generation_endpoint(data: RPSgeneration):
    try:
        result = await rps_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"BPI RPS Script Error: {str(traceback.format_exc())}")


##### payment_allocation_st.py #####
class Payment(BaseModel):
    payment_date: str = Field(..., description="Payment date in DD-MM-YYYY format.")
    amount: float


class PaymentAllocation(BaseModel):
    application_id: int
    payment_list: Payment

    model_config = {
        "json_schema_extra": {
            "example": {
                "application_id": 12474,
                "payment_list": {
                    "payment_date": "30-08-2025",
                    "amount": 9800
                }
            }
        }
    }


@app.post("/api/payment_allocation_st")
async def payment_allocation_endpoint(data: PaymentAllocation):
    try:
        result = await payment_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Reconciliation script Error: {str(traceback.format_exc())}")


##### cam_report_script.py(Vivriti CAM report) & utkarsh_cam.py #####
class CamReports(BaseModel):
    application_id: int = Field(..., description="CF application_id")
    user_id: int = Field(..., description="user id of user who generated the CAM")

    model_config = {
        "json_schema_extra": {
            "example": {
                "application_id": 12390,
                "user_id": 443
            }
        }
    }


@app.post("/api/cam_report_script")
async def vivriti_cam_endpoint(data: CamReports):
    try:
        result = await vivriti_cam_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Vivriti Cam API Error: {str(traceback.format_exc())}")


@app.post("/api/utkarsh_cam")
async def utkarsh_cam_endpoint(data: CamReports):
    try:
        result = await utkarsh_cam_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Utkarsh Cam API Error: {str(traceback.format_exc())}")


UPLOAD_DIR = "pre_part_payment_uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)


@app.post("/api/pre_part_payment")
async def pre_part_payment_endpoint(request: Request,
                                    file: UploadFile = File(..., description="Upload a .csv or .xlsx file.")):
    try:
        # Validate file extension
        if not file.filename.endswith((".csv", ".xlsx")):
            raise HTTPException(status_code=400, detail="Only .csv or .xlsx files are allowed.")

        # Save file with timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        new_filename = f"{timestamp}_{file.filename}"
        file_path = os.path.join(UPLOAD_DIR, new_filename)

        contents = await file.read()
        with open(file_path, "wb") as f:
            f.write(contents)

        # Get ALL form fields dynamically
        form_data = await request.form()
        data = dict(form_data)

        # Add the file path to the collected data
        data["uploaded_file_path"] = file_path

        result = await pre_part_payment_function(data)

        # cleanup uploaded files
        for filename in os.listdir(UPLOAD_DIR):
            os.remove(os.path.join(UPLOAD_DIR, filename))

        return result

    except Exception:
        raise HTTPException(status_code=500, detail=f"Pre-Part Payment Script Error: {str(traceback.format_exc())}")


class OverdueUpdateRequest(BaseModel):
    app_id_list: List[int] = Field(...,
                                   description="List of application IDs to update. If empty, updates all active cases.")
    model_config = {
        "json_schema_extra": {
            "example": {"app_id_list": [8674, 10649, 12903, 10190]}
        }
    }


@app.post("/api/overdue_update")
async def overdue_update_endpoint(data: OverdueUpdateRequest):
    try:
        result = await overdue_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Overdue update Script Error: {traceback.format_exc()}")


@app.post("/api/bounce_penal_update")
async def bouncepenal_update_endpoint(data: OverdueUpdateRequest):
    try:
        result = await bounce_penal_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Bounce-penal update Script Error: {traceback.format_exc()}")


@app.post("/api/update_loan_data")
async def loan_data_bulk_update_endpoint(data: OverdueUpdateRequest):
    try:
        result = await loan_metrics_calculation(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"update_loan_data Script Error: {traceback.format_exc()}")


class ForeclosureRequest(BaseModel):
    application_id: int = Field(..., description="Pass single application_id")
    model_config = {
        "json_schema_extra": {
            "example": {"application_id": 13677}
        }
    }


@app.post("/api/foreclosure_breakup")
async def foreclosure_update_endpoint(data: ForeclosureRequest):
    try:
        result = await foreclosure_breakup_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Foreclosure breakup Script Error: {traceback.format_exc()}")


class SubventionRPSgeneration(BaseModel):
    loan_principal: int = Field(...,
                                description="Net loan amount = Approved amount - upfront from customer(down payment).")
    remaining_emi: int = Field(..., description="net_tenure. emi_calculation.remaining_emi")
    first_emi_date_str: str = Field(..., description="First EMI date in DD-MM-YYYY format.")
    morat_months: int = Field(..., description="Number of months under moratorium (if any, else 0).")
    morat_start_date_str: str = Field(..., description="Start date of moratorium in DD-MM-YYYY format.")

    model_config = {
        "json_schema_extra": {
            "example": {
                "loan_principal": 151560,
                "remaining_emi": 12,
                "first_emi_date_str": "05-08-2025",
                "morat_months": 2,
                "morat_start_date_str": "05-09-2025"
            }
        }
    }


@app.post("/api/subvention_rps")
async def subvention_rps_generation_endpoint(data: SubventionRPSgeneration):
    try:
        result = await subvention_rps_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Subvention RPS Script Error: {str(traceback.format_exc())}")


class SecondaryStatus(str, Enum):
    SETTLEMENT_IN_PROGRESS = "settlement-in-progress"
    SETTLED = "settled"
    SETTLEMENT_DONE = "settlement-done"

class SettlementPayment(BaseModel):
    application_id: int = Field(..., description="Application ID")
    payment_date: str = Field(..., description="Payment date in YYYY-MM-DD format.")
    amount: int = Field(..., description="Settlement amount")
    secondary_status: SecondaryStatus = Field(..., description="Settlement secondary status")
    is_verify: int = Field(..., description="Check if api is being called before(0) or at(1) verification. Values 0 or 1.")

    model_config = {
        "extra": "allow",  # <-- THIS ALLOWS ANY EXTRA FIELDS
        "json_schema_extra": {
            "example": {
                "application_id": 12560,
                "payment_date": "2026-02-04",
                "amount": 10000,
                "secondary_status": "settlement-in-progress",
                "is_verify": 0
            }
        }
    }


@app.post("/api/settlement_reconcile")
async def settlement_endpoint(data: SettlementPayment):
    try:
        result = await settlement_reconcile_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Settlement reconcile Script Error: {str(traceback.format_exc())}")


class ForeclosureAllocation(BaseModel):
    application_id: int = Field(..., description="Application ID")
    amount: int = Field(..., description="Total foreclosure amount received")
    payment_date: str = Field(..., description="Payment date in YYYY-MM-DD format.")

    model_config = {
        "extra": "allow",  # allow the rest of the payment fields (mode, trasanction_id, cf_transaction_id, etc.)
        "json_schema_extra": {
            "example": {
                "application_id": 13677,
                "amount": 100000,
                "payment_date": "2026-06-29"
            }
        }
    }


@app.post("/api/foreclosure_allocation")
async def foreclosure_allocation_endpoint(data: ForeclosureAllocation):
    try:
        result = await foreclosure_allocation_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Foreclosure allocation Script Error: {str(traceback.format_exc())}")


EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")


def validate_email_list(emails: List[str]):
    """Ensure each email is valid and return the cleaned list."""
    clean_emails = []
    for email in emails:
        email = email.strip()
        if not EMAIL_REGEX.match(email):
            raise HTTPException(status_code=400, detail=f"Invalid email address: {email}")
        clean_emails.append(email)
    return clean_emails


@app.get("/dpd-report")
async def dpd_report_endpoint(
        lender_id: Optional[int] = Query(None),
        merchant_id: Optional[int] = Query(None),
        anchor_merchant: Optional[int] = Query(None),
        merchant_category: Optional[int] = Query(None),
        advisor: Optional[int] = Query(None),
        disbursement_date_from: Optional[str] = Query(None, description="Start disbursement date in DD-MM-YYYY format"),
        disbursement_date_to: Optional[str] = Query(None, description="End disbursement date in DD-MM-YYYY format"),
        unpaid_charges: Optional[int] = Query(0),
        # email_list: List[str] = Query(..., description="Provide at least one email")
        email_list: Union[List[str], str] = Query(..., description="Provide at least one email"),
):
    # Handle both JSON-style and repeated-query formats for email_list
    if isinstance(email_list, str):
        try:
            parsed = json.loads(email_list)
            if isinstance(parsed, list):
                email_list = parsed
            else:
                email_list = [parsed]
        except json.JSONDecodeError:
            email_list = [email_list]
    # Validate emails
    email_list = validate_email_list(email_list)

    # Validate dates
    for date_str in [disbursement_date_from, disbursement_date_to]:
        if date_str:
            try:
                datetime.strptime(date_str, "%d-%m-%Y")
            except ValueError:
                raise HTTPException(status_code=400, detail=f"Date must be in DD-MM-YYYY format: {date_str}")

    # Build request data
    data = {}
    if lender_id is not None:
        data["lender_id"] = [lender_id]
    if merchant_id is not None:
        data["merchant_id"] = [merchant_id]
    if anchor_merchant is not None:
        data["anchor_merchant"] = [anchor_merchant]
    if merchant_category is not None:
        data["merchant_category"] = [merchant_category]
    if advisor is not None:
        data["advisor"] = [advisor]
    if disbursement_date_from is not None:
        data["disbursement_date_from"] = disbursement_date_from
    if disbursement_date_to is not None:
        data["disbursement_date_to"] = disbursement_date_to
    data["unpaid_charges"] = unpaid_charges
    data["email_list"] = email_list

    try:
        result = await dpd_report_function(data)
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"DPD report Script Error: {traceback.format_exc()}")

# Commented due to CRED-6386 Homeville Transition
# @app.post("/api/homeville_rps_update")
# async def homeville_rps_update_endpoint(data: OverdueUpdateRequest):
#     try:
#         result = await homeville_rps_update_function(data.model_dump())
#         return result
#     except Exception:
#         raise HTTPException(status_code=500, detail=f"Homeville RPS Script Error: {traceback.format_exc()}")


class PrepartPaymentV2(BaseModel):
    application_id: int = Field(..., description="Application ID")
    payment_date: str = Field(..., description="Payment date in YYYY-MM-DD format.")
    amount: int = Field(..., description="Pre part payment amount")
    is_preview_flag: Literal[0, 1] = Field(..., description="1 for preview, 0 for update")
    type: Literal["emi", "tenure"] = Field(..., description="Either 'emi' or 'tenure'")
    user_id: int = Field(..., description="User ID")

    model_config = {
        "extra": "allow",  # allows any other extra fields
        "json_schema_extra": {
            "example": {
                "application_id": 13446,
                "payment_date": "2025-12-27",
                "amount": 5000,
                "is_preview_flag": 1,
                "type": "tenure",
                "user_id": 107
            }
        }
    }


@app.post("/api/pre_part_payment_v2")
async def pre_part_payment_v2_endpoint(data: PrepartPaymentV2):
    try:
        result = await pre_part_payment_v2_function(data.model_dump())
        return result
    except Exception:
        raise HTTPException(status_code=500, detail=f"Pre-part payment v2 Script Error: {str(traceback.format_exc())}")


@app.post("/api/blacksoil_rps_upload")
async def blacksoil_rps_upload_endpoint(request: Request,
                                        is_verify: int = Form(..., description="Flag to indicate if it's a verify run (0) or full run (1)"),
                                        file: UploadFile = File(..., description="Upload a .csv or .xlsx file.")):
    try:
        # Validate file extension
        if not file.filename.endswith((".csv", ".xlsx")):
            raise HTTPException(status_code=400, detail="Only .csv or .xlsx files are allowed.")

        # Save file with timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        new_filename = f"blacksoil_{timestamp}_{file.filename}"
        file_path = os.path.join(UPLOAD_DIR, new_filename)

        contents = await file.read()
        with open(file_path, "wb") as f:
            f.write(contents)

        # Get ALL form fields dynamically
        form_data = await request.form()
        data = dict(form_data)

        # Add the file path to the collected data
        data["uploaded_file_path"] = file_path

        result = await blacksoil_rps_function(data)

        # cleanup uploaded file
        if os.path.exists(file_path):
            os.remove(file_path)

        return result

    except Exception:
        raise HTTPException(status_code=500, detail=f"BlackSoil RPS Upload Script Error: {str(traceback.format_exc())}")


@app.post("/api/blacksoil_payments_upload")
async def blacksoil_payments_upload_endpoint(request: Request,
                                             background_tasks: BackgroundTasks,
                                             is_verify: int = Form(..., description="Flag to indicate if it's a verify run (0) or full run (1)"),
                                           file: UploadFile = File(..., description="Upload a .csv or .xlsx file.")):
    try:
        # Validate file extension
        if not file.filename.endswith((".csv", ".xlsx")):
            raise HTTPException(status_code=400, detail="Only .csv or .xlsx files are allowed.")

        # Save file with timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        new_filename = f"blacksoil_payments_{timestamp}_{file.filename}"
        file_path = os.path.join(UPLOAD_DIR, new_filename)

        contents = await file.read()
        with open(file_path, "wb") as f:
            f.write(contents)

        # Get ALL form fields dynamically
        form_data = await request.form()
        data = dict(form_data)

        # Add the file path to the collected data
        data["uploaded_file_path"] = file_path

        result = await blacksoil_payments_function(data)

        # Trigger accrued revenue background task if payment upload was fully applied
        if result.get('status') == 'SUCCESS' and is_verify == 1:
            loan_ids = result.get('loan_ids')
            start_date = result.get('min_payment_date')
            if loan_ids:
                background_tasks.add_task(calculate_accrued_revenue, loan_ids, None, start_date, False)

        # cleanup uploaded file
        if os.path.exists(file_path):
            os.remove(file_path)

        return result

    except Exception:
        raise HTTPException(status_code=500, detail=f"BlackSoil Payments Upload Script Error: {str(traceback.format_exc())}")


class DARevenueRequest(BaseModel):
    loan_ids: Optional[List[str]] = Field(default=None, description="List of loan IDs to recalculate. If empty, calculates for all active loans.")
    as_on_date: Optional[str] = Field(None, description="Date in YYYY-MM-DD format.")
    start_date: Optional[str] = Field(None, description="Start date for updating metrics in YYYY-MM-DD format.")
    recalculate_history: bool = Field(default=False, description="Whether to recalculate entire history.")

@app.post("/api/calculate-blacksoil-da-accrued-revenue")
async def calculate_da_accrued_revenue_endpoint(data: DARevenueRequest, background_tasks: BackgroundTasks):
    try:
        background_tasks.add_task(calculate_accrued_revenue, data.loan_ids, data.as_on_date, data.start_date, data.recalculate_history)
        return {"message": f"Job accepted. Recalculating metrics for {'all' if not data.loan_ids else len(data.loan_ids)} loans in the background."}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error starting calculate-da-accrued-revenue: {str(traceback.format_exc())}")


######################## Merchant risk old code #####################
from starlette.background import BackgroundTask
import aiofiles
from tool import notebook_executor
from fastapi.responses import FileResponse
import shutil, zipfile


@app.post("/merchant-risk")
async def dynamic_api(request: Request):
    try:

        empty_folder("./script/merchant_risk/input")
        empty_folder("./script/merchant_risk/output")

        form = await request.form()

        folder_path = "./script/merchant_risk/input"
        os.makedirs(folder_path, exist_ok=True)

        for key, value in form.items():
            if isinstance(value, UploadFile):
                cleaned_key = key.replace("files[", "").replace("]", "")
                _, ext = os.path.splitext(value.filename)
                filename = f"{cleaned_key}{ext}"
                file_path = os.path.join(folder_path, filename)

                value.file.seek(0)  # optional: reset file pointer

                async with aiofiles.open(file_path, "wb") as out_file:
                    while chunk := await value.read(1024):  # read in chunks
                        await out_file.write(chunk)
            else:
                print("No file format")

        notebook_executor.run_notebook(form)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(traceback.format_exc())}")


@app.get("/download-zip")
def download_zip_and_clean():
    folder_to_zip = "./script/merchant_risk/output"
    zip_path = "output.zip"

    zip_folder(folder_to_zip, zip_path)

    return FileResponse(
        path=zip_path,
        media_type='application/zip',
        filename="output.zip",
        background=BackgroundTask(lambda: os.remove(zip_path))
    )


def empty_folder(folder_path: str):
    for filename in os.listdir(folder_path):
        file_path = os.path.join(folder_path, filename)
        try:
            if os.path.isfile(file_path) or os.path.islink(file_path):
                os.unlink(file_path)  # delete file or symlink
            elif os.path.isdir(file_path):
                shutil.rmtree(file_path)  # delete folder
        except Exception as e:
            print(f"Failed to delete {file_path}. Reason: {e}")


def zip_folder(folder_path: str, output_zip_path: str):
    with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, _, files in os.walk(folder_path):
            for file in files:
                file_path = os.path.join(root, file)
                # Archive file relative to the folder (no absolute paths)
                arcname = os.path.relpath(file_path, start=folder_path)
                zipf.write(file_path, arcname)


@app.post("/overdue-reminder")
async def run_final_overdue_reminder(request: Request,background_tasks: BackgroundTasks):
    try:
        data = await request.json()
        application_ids = data.get("application_id", [])
        collectionEmail = data.get("collectionEmail", "ashish.gupta@creditfair.in")
        if not application_ids:
            raise HTTPException(status_code=400, detail="Missing 'application_id' list")
        background_tasks.add_task(overdue_main, application_ids,collectionEmail)
        return {"message": f"Job accepted. Processing {len(application_ids)} applications in the background."}
    except Exception as e:
        traceback_str = traceback.format_exc()
        raise HTTPException(status_code=500, detail=f"Error running Final_Overdue_Reminder: {traceback_str}")


@app.post("/api/process-ptp-excel")
async def run_ptp_excel_processing(
    file: UploadFile = File(...),
    user_id: str = Form(...),
):
    try:
        file_content = await file.read()

        if not file_content:
            raise HTTPException(status_code=400, detail="Uploaded file is empty")

        response = process_ptp_excel(file_content, user_id, file.filename)

        if response.get("status") == "error":
            raise HTTPException(status_code=400, detail=response.get("message"))

        return {"message": response.get("message"), "data": response}

    except HTTPException:
        raise
    except Exception as e:
        traceback_str = traceback.format_exc()
        raise HTTPException(
            status_code=500, detail=f"Error running process_ptp_excel: {traceback_str}"
        )


json_example = """[
    {
        "loan_id": 102,
        "loan_amount": 100000,
        "interest_rate": 0.29,
        "tenure": 12,
        "disbursement_date": "2026-06-01",
        "first_emi_date": "2026-07-03",
        "product_id": 1
    }
]"""

@app.post("/api/co_lending_rps_bulk")
async def co_lending_rps_bulk_endpoint(request: Request,
                                        user_id: Optional[int] = Form(107, description="User ID for creating the records"),
                                        file: Union[UploadFile, str, None] = File(None, description="Upload a .csv or .xlsx file."),
                                        json_data: Optional[str] = Form(None, description="Optional: Paste a JSON list of dictionaries here.", example=json_example)):
    try:
        # 1. Check if raw JSON string was pasted in the form field (Swagger UI)
        if json_data:
            try:
                loan_data = json.loads(json_data)
            except json.JSONDecodeError:
                raise HTTPException(status_code=400, detail="Invalid JSON string provided in json_data field.")
            if not isinstance(loan_data, list):
                raise HTTPException(status_code=400, detail="JSON body must be a list of dictionaries.")
            result = co_lending_bulk_rps_function(loan_data, user_id=user_id)
            return {"message": "Success", "data": result}

        # 2. Check if a direct application/json request was sent (Postman)
        content_type = request.headers.get("content-type", "")
        if "application/json" in content_type:
            loan_data = await request.json()
            if not isinstance(loan_data, list):
                raise HTTPException(status_code=400, detail="JSON body must be a list of dictionaries.")
            result = co_lending_bulk_rps_function(loan_data, user_id=user_id)
            return {"message": "Success", "data": result}
            
        # 3. Finally, check if a valid file was uploaded
        if not file or isinstance(file, str) or getattr(file, "filename", "") == "":
            raise HTTPException(status_code=400, detail="Must provide either a JSON body/string or a file upload.")

        # Validate file extension
        if not file.filename.endswith((".csv", ".xlsx")):
            raise HTTPException(status_code=400, detail="Only .csv or .xlsx files are allowed.")

        # Save file with timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        new_filename = f"colending_rps_{timestamp}_{file.filename}"
        file_path = os.path.join(UPLOAD_DIR, new_filename)

        contents = await file.read()
        with open(file_path, "wb") as f:
            f.write(contents)

        result = co_lending_bulk_rps_function(file_path, user_id=user_id)

        # cleanup uploaded file
        if os.path.exists(file_path):
            os.remove(file_path)

        return {"message": "Success", "data": result}

    except HTTPException:
        raise
    except Exception:
        raise HTTPException(status_code=500, detail=f"Co-Lending RPS Bulk Upload Error: {str(traceback.format_exc())}")


@app.post("/api/merchant_collection_report")
async def trigger_merchant_collection_report(background_tasks: BackgroundTasks):
    """
    Background job to generate and send pending collection reports to merchants with >5 days delay.
    """
    try:
        background_tasks.add_task(generate_merchant_collection_report)
        return {"message": "Merchant collection report job started in the background"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Merchant Collection Report Error: {str(traceback.format_exc())}")


json_payment_example = """[
    {
        "loan_id": 60000,
        "payment_date": "30-08-2026",
        "amount": 10000,
        "payment_for": "EMI",
        "transaction_number": "blacksoil123",
        "transaction_mode": "NEFT",
        "utr": "IDFC232"
    }
]"""

@app.post("/api/co_lending_regular_allocation_bulk")
async def co_lending_regular_allocation_bulk_endpoint(request: Request,
                                        background_tasks: BackgroundTasks,
                                        user_id: Optional[int] = Form(107, description="User ID for creating the records"),
                                        file: Union[UploadFile, str, None] = File(None, description="Upload a .csv or .xlsx file."),
                                        json_data: Optional[str] = Form(None, description="Optional: Paste a JSON list of dictionaries here.", example=json_payment_example)):
    try:
        # 1. Check if raw JSON string was pasted in the form field (Swagger UI)
        if json_data:
            try:
                loan_data = json.loads(json_data)
            except json.JSONDecodeError:
                raise HTTPException(status_code=400, detail="Invalid JSON string provided in json_data field.")
            if not isinstance(loan_data, list):
                raise HTTPException(status_code=400, detail="JSON body must be a list of dictionaries.")
            background_tasks.add_task(co_lending_bulk_allocation_function, loan_data, user_id)
            return {"message": "Bulk allocation processing initiated in the background."}

        # 2. Check if a direct application/json request was sent (Postman)
        content_type = request.headers.get("content-type", "")
        if "application/json" in content_type:
            loan_data = await request.json()
            if not isinstance(loan_data, list):
                raise HTTPException(status_code=400, detail="JSON body must be a list of dictionaries.")
            background_tasks.add_task(co_lending_bulk_allocation_function, loan_data, user_id)
            return {"message": "Bulk allocation processing initiated in the background."}
            
        # 3. Finally, check if a valid file was uploaded
        if not file or isinstance(file, str) or getattr(file, "filename", "") == "":
            raise HTTPException(status_code=400, detail="Must provide either a JSON body/string or a file upload.")

        # Validate file extension
        if not file.filename.endswith((".csv", ".xlsx")):
            raise HTTPException(status_code=400, detail="Only .csv or .xlsx files are allowed.")

        # Save file with timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        new_filename = f"colending_allocation_{timestamp}_{file.filename}"
        file_path = os.path.join(UPLOAD_DIR, new_filename)

        contents = await file.read()
        with open(file_path, "wb") as f:
            f.write(contents)

        def background_process_and_cleanup(fp, uid):
            try:
                co_lending_bulk_allocation_function(fp, user_id=uid)
            finally:
                if os.path.exists(fp):
                    os.remove(fp)

        background_tasks.add_task(background_process_and_cleanup, file_path, user_id)

        return {"message": "Bulk allocation processing initiated in the background."}

    except HTTPException:
        raise
    except Exception:
        raise HTTPException(status_code=500, detail=f"Co-Lending Bulk Allocation Error: {str(traceback.format_exc())}")


@app.post("/api/co_lending_parked_cron")
async def co_lending_parked_cron_endpoint(background_tasks: BackgroundTasks):
    try:
        # We process this in background so it doesn't block the API response
        background_tasks.add_task(process_parked_funds)
        return {"message": "Parked funds processing initiated in the background."}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error starting parked cron: {str(traceback.format_exc())}")

class RefreshMetrics(BaseModel):
    loan_ids: List[str] = Field(default_factory=list, description="List of loan IDs to refresh metrics for. Leave empty to refresh all.")

@app.post("/api/co_lending_refresh_metrics")
async def co_lending_refresh_metrics_endpoint(
    request: RefreshMetrics,
    # background_tasks: BackgroundTasks
):
    try:
        from scripts.blacksoil_lms.co_lending_bounce_penal import process_bounce_penal_updates
        # background_tasks.add_task(process_bounce_penal_updates, request.loan_ids)
        # Run it synchronously so it blocks until the DB is updated
        process_bounce_penal_updates(request.loan_ids) 
        return {"message": "Metrics refreshed successfully."}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error starting metrics refresh: {str(traceback.format_exc())}")

@app.post("/api/co_lending_daily_metrics_cron")
async def co_lending_daily_metrics_cron_endpoint(background_tasks: BackgroundTasks):
    try:
        from scripts.blacksoil_lms.co_lending_daily_metrics_cron import process_daily_metrics
        background_tasks.add_task(process_daily_metrics)
        return {"message": "Daily metrics cron initiated in the background."}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error starting daily metrics cron: {str(traceback.format_exc())}")

class HistoricalMetricsRequest(BaseModel):
    loan_ids: List[str]
    from_date: str = Field(..., description="Format: YYYY-MM-DD")
    to_date: Optional[str] = Field(None, description="Format: YYYY-MM-DD. Defaults to today.")

    class Config:
        schema_extra = {
            "example": {
                "loan_ids": ["101", "104"],
                "from_date": "2026-07-01"
            }
        }

@app.post("/api/co_lending_recalculate_historical_metrics")
async def co_lending_recalculate_historical_metrics_endpoint(
    request: HistoricalMetricsRequest,
    background_tasks: BackgroundTasks
):
    try:
        from scripts.blacksoil_lms.co_lending_historical_recalc import recalculate_historical_metrics
        background_tasks.add_task(recalculate_historical_metrics, request.loan_ids, request.from_date, request.to_date)
        return {"message": "Historical metrics recalculation initiated in the background."}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error starting historical metrics recalculation: {str(traceback.format_exc())}")


@app.post("/api/process-pincode-master")
async def run_pincode_master_processing(
    file: UploadFile = File(...),
    user_id: str = Form(...),
):
    try:
        file_content = await file.read()

        if not file_content:
            raise HTTPException(status_code=400, detail="Uploaded file is empty")

        response = process_pincode_master_excel(file_content, user_id, file.filename)

        if response.get("status") == "error":
            raise HTTPException(status_code=400, detail=response.get("message"))

        return {"message": response.get("message"), "data": response}

    except HTTPException:
        raise
    except Exception as e:
        traceback_str = traceback.format_exc()
        raise HTTPException(
            status_code=500, detail=f"Error running process_pincode_master_excel: {traceback_str}"
        )


class AccruedRevenueRequest(BaseModel):
    app_id_list: List[int] = Field(default=[], description="List of application IDs to update. If empty, processes all active cases.")
    model_config = {
        "json_schema_extra": {
            "example": {"app_id_list": [287532, 290861, 275066]}
        }
    }

@app.post("/api/accrued_revenue_report")
async def accrued_revenue_report_endpoint(data: AccruedRevenueRequest, background_tasks: BackgroundTasks):
    try:
        background_tasks.add_task(generate_accrued_revenue_report, data.app_id_list)
        return {"message": "Accrued Revenue Report processing initiated in the background."}
    except Exception:
        raise HTTPException(status_code=500, detail=f"Accrued Revenue Report Error: {str(traceback.format_exc())}")


class CollectionReportRequest(BaseModel):
    app_id_list: Optional[List[int]] = Field(default=None, description="Optional list of application IDs to run report for.")
    from_date: Optional[str] = Field(default=None, description="Start date in YYYY-MM-DD format.")
    to_date: Optional[str] = Field(default=None, description="End date in YYYY-MM-DD format.")

@app.post("/api/generate_collection_report")
async def collection_report_endpoint(data: CollectionReportRequest, background_tasks: BackgroundTasks):
    try:
        background_tasks.add_task(generate_collection_report_job, data.model_dump())
        return {"message": "Collection report job initiated in the background."}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error starting collection report: {str(traceback.format_exc())}")


if __name__ == "__main__":
    import uvicorn

    uvicorn.run("app:app", host="127.0.0.1", port=5001,
                reload=True,
                log_level="debug")
