from fastapi import APIRouter, UploadFile, File, BackgroundTasks, Response, HTTPException, Depends, Form
from fastapi.responses import FileResponse
from typing import Optional, List
import os
import io
import pandas as pd
from src.core.rbac.rbac_dependency import get_current_user_payload
from core_utilities import mail_service
import uuid
from datetime import datetime
from src.application.services.merchant_risk.risk_engine import RiskEngine
from src.application.use_cases.merchant_risk.process_gross_risk import ProcessGrossRiskUseCase
from src.application.use_cases.merchant_risk.process_after_adjustment import ProcessAfterAdjustmentUseCase
from src.application.use_cases.merchant_risk.process_consolidated_mis import ProcessConsolidatedMISUseCase
from src.application.use_cases.merchant_risk.process_portfolio_cuts import ProcessPortfolioCutsUseCase
from src.application.use_cases.merchant_risk.process_static_pool import ProcessStaticPoolUseCase
from src.domain.entities.risk_task import RiskTask, TaskStatus
from src.infrastructure.repositories.risk_task_repository import RiskTaskRepository
from src.core.logger import get_cloudwatch_logger, log_progress
from src.config.settings import settings
import zipfile
import time
# import boto3

# def upload_to_s3_and_get_presigned_url(file_path: str, bucket_name: str, object_name: str):
#     """Uploads a file to S3 and returns a 30-minute pre-signed URL."""
#     s3_client = boto3.client(
#         's3',
#         aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
#         aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
#         region_name=os.environ.get("AWS_REGION_NAME", "ap-south-1")
#     )
#     try:
#         s3_client.upload_file(file_path, bucket_name, object_name)
#         url = s3_client.generate_presigned_url(
#             'get_object',
#             Params={'Bucket': bucket_name, 'Key': object_name},
#             ExpiresIn=1800  # 30 minutes
#         )
#         return url
#     except Exception as e:
#         print(f"S3_UPLOAD_ERROR: {e}")
#         return None


def remove_file(path: str):
    """Utility to remove a file from disk."""
    try:
        if os.path.exists(path):
            os.remove(path)
    except Exception as e:
        print(f"Error removing file {path}: {e}")

def purge_old_reports(reports_dir: str, max_age_seconds: int = 3600):
    """Removes files older than max_age_seconds from the reports directory."""
    try:
        if not os.path.exists(reports_dir):
            return
        now = time.time()
        for f in os.listdir(reports_dir):
            f_path = os.path.join(reports_dir, f)
            if os.path.isfile(f_path):
                if os.stat(f_path).st_mtime < now - max_age_seconds:
                    remove_file(f_path)
    except Exception as e:
        print(f"Error purging old reports: {e}")

router = APIRouter(prefix="/merchant-risk", tags=["Merchant Risk"])
repository = RiskTaskRepository()

def run_risk_calculation_task(
    task_id: str, 
    master_content: bytes, 
    lender_content: bytes,
    lender_name_change_content: bytes,
    adjustments_data_content: bytes,
    previous_month_content: bytes,
    fintech_content: bytes,
    jify_state_content: bytes,
    jify_score_content: bytes,
    user_email: str = None
):
    """Background task for sequential Before, After & Consolidated MIS calculation."""
    task = repository.get_by_id(task_id)
    if not task: return

    try:
        task.status = TaskStatus.PROCESSING
        task.message = "Loading data engines..."
        task.progress = 0.05
        task.updated_at = datetime.now()
        repository.save(task)

        cw_logger = get_cloudwatch_logger("merchant-risk")

        # Step 1: Before Adjustment
        risk_engine = RiskEngine()
        gross_use_case = ProcessGrossRiskUseCase(risk_engine)
        log_progress(cw_logger, user_email, "ProcessGrossRisk", "STARTED")
        t0 = time.time()
        before_adj_csv = gross_use_case.execute(
            master_content, 
            lender_content,
            lender_name_change_content,
            adjustments_data_content
        )
        log_progress(cw_logger, user_email, "ProcessGrossRisk", "COMPLETED", time.time() - t0)

        reports_dir = os.path.join(os.getcwd(), "temp_reports")
        if not os.path.exists(reports_dir): os.makedirs(reports_dir)

        # Extract analysis month for cleaner filenames as requested
        try:
            df_temp = pd.read_csv(io.BytesIO(before_adj_csv), usecols=['analysis_date'], nrows=1)
            analysis_dt = pd.to_datetime(df_temp['analysis_date'].iloc[0])
            analysis_month = analysis_dt.strftime('%b_%Y')
        except:
            analysis_month = datetime.now().strftime('%b_%Y')

        id_suffix = f"_{task_id}"
        before_filename = f"merchant_risk_before_adj_{analysis_month}{id_suffix}.csv"
        with open(os.path.join(reports_dir, before_filename), "wb") as f:
            f.write(before_adj_csv)
        
        task.result_files.append(before_filename)
        repository.save(task)

        task.message = "Generating After-Adjustment data..."
        task.progress = 0.3
        repository.save(task)
        
        # Step 2: After Adjustment (Sequential)
        # We reuse the generated 'before_adj_csv' as requested
        after_use_case = ProcessAfterAdjustmentUseCase(risk_engine)
        log_progress(cw_logger, user_email, "ProcessAfterAdjustment", "STARTED")
        t0 = time.time()
        after_adj_csv = after_use_case.execute(
            before_adj_csv, 
            adjustments_data_content,
            previous_month_content,
            lender_content
        )
        log_progress(cw_logger, user_email, "ProcessAfterAdjustment", "COMPLETED", time.time() - t0)

        after_filename = f"merchant_risk_after_adj_{analysis_month}{id_suffix}.csv"
        with open(os.path.join(reports_dir, after_filename), "wb") as f:
            f.write(after_adj_csv)
        
        task.result_files.append(after_filename)
        repository.save(task)

        task.message = "Generating Consolidated MIS & Charts..."
        task.progress = 0.5
        repository.save(task)
        
        # Step 3: Consolidated MIS (Sequential)
        mis_use_case = ProcessConsolidatedMISUseCase()
        
        log_progress(cw_logger, user_email, "ProcessConsolidatedMIS", "STARTED")
        t0 = time.time()
        output_xlsx_path, chart_paths = mis_use_case.execute(fintech_content, after_adj_content=after_adj_csv, task_id=task_id)
        log_progress(cw_logger, user_email, "ProcessConsolidatedMIS", "COMPLETED", time.time() - t0)
        mis_filename = os.path.basename(output_xlsx_path)

        task.result_files.append(mis_filename)
        # Also add charts for cleanup (but don't include them in the ZIP if not desired, 
        # actually notebook inserts them INTO the excel, so they are redundant in the zip 
        # if the user only wants the excel and the zip. But wait, the user said 
        # "when cleaning up temp_reports folder, remove the portfolio mix and onbook-offbook charts too".
        # This means they are generated and should be removed.
        for chart_path in chart_paths:
            task.result_files.append(os.path.basename(chart_path))
        
        task.message = "Processing Portfolio Cuts..."
        task.progress = 0.7
        repository.save(task)
        
        # Step 4: Portfolio Cuts (Sequential) - NEW PHASE 7
        cuts_use_case = ProcessPortfolioCutsUseCase()
        # The notebook uses Jify POS for cuts, we pass it if available
        log_progress(cw_logger, user_email, "ProcessPortfolioCuts", "STARTED")
        t0 = time.time()
        portfolio_cut_files = cuts_use_case.execute(
            after_adj_content=after_adj_csv,
            fintech_content=fintech_content,
            jify_state_content=jify_state_content,
            jify_score_content=jify_score_content,
            task_id=task_id
        )
        log_progress(cw_logger, user_email, "ProcessPortfolioCuts", "COMPLETED", time.time() - t0)
        
        for cut_file in portfolio_cut_files:
            task.result_files.append(os.path.basename(cut_file))

        task.message = "Generating Static Pool / Vintage Curves..."
        task.progress = 0.9
        repository.save(task)
        
        # Step 5: Static Pool (Sequential)
        static_pool_use_case = ProcessStaticPoolUseCase()
        log_progress(cw_logger, user_email, "ProcessStaticPool", "STARTED")
        t0 = time.time()
        static_pool_files = static_pool_use_case.execute(
            after_adj_content=after_adj_csv,
            fintech_content=fintech_content,
            task_id=task_id
        )
        log_progress(cw_logger, user_email, "ProcessStaticPool", "COMPLETED", time.time() - t0)
        for sp_file in static_pool_files:
            task.result_files.append(os.path.basename(sp_file))

        task.message = "Finalizing ZIP bundle and sending email..."
        task.progress = 0.95
        repository.save(task)
        
        # Bundle everything into a ZIP
        zip_filename = f"merchant_risk_reports_{task_id}.zip"
        zip_path = os.path.join(reports_dir, zip_filename)
        
        # Keep track of files to delete (all files generated in this task except the zip itself)
        files_to_cleanup = list(task.result_files)
        
        with zipfile.ZipFile(zip_path, 'w') as zipf:
            for f in task.result_files:
                f_path = os.path.join(reports_dir, f)
                if os.path.exists(f_path):
                    zipf.write(f_path, f)
        
        # Send Email Notification via SES if user_email is provided
        if user_email:
            zip_size = os.path.getsize(zip_path) if os.path.exists(zip_path) else 0
            try:
                now_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                subject = f"Merchant Risk Report {now_str}"
                
                # SES Attachment limit is 10MB (base64 overhead means ~7.5MB functional limit)
                if zip_size > 7 * 1024 * 1024:
                    download_link = f"{settings.BACKEND_API_URL}/api/v1/merchant-risk/download/{task_id}?file_name={zip_filename}"
                    message = f"Hi Team,<br><br>The Merchant Risk reports generated are too large to email as an attachment ({zip_size / (1024*1024):.2f} MB).<br><br>You can download the zip file using the secure link below (Note: This is a one-time download link):<br><br><a href='{download_link}'>Download Reports</a>"
                    mail_result = mail_service.send_mail([user_email], message, subject)
                    
                    # Original S3 logic commented out for future reference
                    # bucket = os.environ.get("AWS_BUCKET")
                    # s3_object_name = f"merchant_risk/reports/{zip_filename}"
                    # presigned_url = upload_to_s3_and_get_presigned_url(zip_path, bucket, s3_object_name)
                    # if presigned_url:
                    #     message = f"Hi Team,<br><br>The Merchant Risk reports generated are too large to email as an attachment ({zip_size / (1024*1024):.2f} MB).<br><br>You can download the zip file using the secure link below (valid for 30 minutes):<br><br><a href='{presigned_url}'>link</a>"
                    #     mail_result = mail_service.send_mail([user_email], message, subject)
                    # else:
                    #     mail_result = False
                else:
                    message = f"Hi Team,<br><br>Please find attached the Merchant Risk reports generated."
                    mail_result = mail_service.send_mail_with_attachment([user_email], message, subject, zip_path)
                
                if mail_result:
                    print(f"REPORT_MAIL: Merchant risk reports shared via email successfully to {user_email}")
                else:
                    print(f"REPORT_MAIL: Failed to share report via email to {user_email}")
            except Exception as mail_err:
                print(f"REPORT_MAIL_ERROR: {str(mail_err)}")
        else:
            print(f"REPORT_MAIL_DEBUG: No user_email provided in task. Skipping email.")

        # Cleanup intermediate files
        for f in files_to_cleanup:
            f_path = os.path.join(reports_dir, f)
            try:
                if os.path.exists(f_path):
                    os.remove(f_path)
            except:
                pass # Ignore cleanup errors
                
        # Only include the zip file in results for a single download
        task.result_files = [zip_filename]

        task.status = TaskStatus.COMPLETED
        task.updated_at = datetime.now()
        repository.save(task)

    except Exception as e:
        import traceback
        print(f"ERROR in run_risk_calculation_task: {str(e)}")
        traceback.print_exc()
        task.status = TaskStatus.FAILED
        task.error_message = str(e)
        task.updated_at = datetime.now()
        repository.save(task)

@router.post("/calculate-risk")
async def calculate_risk_async(
    background_tasks: BackgroundTasks,
    master_data: UploadFile = File(...),
    fintech_sheet: UploadFile = File(...),
    lender_data: UploadFile = File(...),
    lender_name_change: UploadFile = File(...),
    adjustments_data: UploadFile = File(...),
    previous_month_mr: UploadFile = File(...),
    jify_state: UploadFile = File(...),
    jify_score: UploadFile = File(...),
    user_email: str = Form(...)
):
    # Purge any orphaned reports older than 1 hour before starting new task
    reports_dir = os.path.join(os.getcwd(), "temp_reports")
    background_tasks.add_task(purge_old_reports, reports_dir)

    print(f"API_DEBUG: Received calculate-risk request from {user_email}")

    task_id = str(uuid.uuid4())
    master_content = await master_data.read()
    fintech_content = await fintech_sheet.read()
    lender_content = await lender_data.read()
    lender_name_change_content = await lender_name_change.read()
    adjustments_data_content = await adjustments_data.read()
    previous_month_content = await previous_month_mr.read()
    jify_state_content = await jify_state.read()
    jify_score_content = await jify_score.read()

    task = RiskTask(
        task_id=task_id,
        status=TaskStatus.PENDING,
        created_at=datetime.now(),
        updated_at=datetime.now(),
        message="Queuing task...",
        progress=0.0,
        result_files=[]
    )
    repository.save(task)

    background_tasks.add_task(
        run_risk_calculation_task, 
        task_id, 
        master_content, 
        lender_content,
        lender_name_change_content,
        adjustments_data_content,
        previous_month_content,
        fintech_content,
        jify_state_content,
        jify_score_content,
        user_email
    )

    return {"task_id": task_id, "status": task.status}

@router.get("/tasks/{task_id}")
async def get_task_status(task_id: str):
    task = repository.get_by_id(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return task

@router.get("/tasks", response_model=List[RiskTask])
async def list_tasks():
    return repository.list_all()

@router.get("/download/{task_id}")
async def download_task_result(task_id: str, background_tasks: BackgroundTasks, file_name: Optional[str] = None):
    task = repository.get_by_id(task_id)
    if not task or task.status != TaskStatus.COMPLETED:
        raise HTTPException(status_code=404, detail="Result not ready")
    
    # Default to first file if not specified
    target_file = file_name or (task.result_files[0] if task.result_files else None)
    
    if not target_file or target_file not in task.result_files:
        raise HTTPException(status_code=404, detail="File not found in task")
    
    file_path = os.path.join(os.getcwd(), "temp_reports", target_file)
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="File missing on server")

    # Schedule removal after response is sent
    background_tasks.add_task(remove_file, file_path)

    return FileResponse(
        path=file_path,
        filename=target_file,
        media_type="application/zip" if target_file.endswith(".zip") else "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" if target_file.endswith(".xlsx") else "text/csv"
    )

@router.get("/samples/{filename}")
async def download_sample_file(filename: str):
    """Serves sample files for merchant risk reports."""
    sample_dir = os.path.join(os.getcwd(), "src", "assets", "merchant_risk_samples")
    file_path = os.path.join(sample_dir, filename)
    
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="Sample file not found")
        
    return FileResponse(
        path=file_path,
        filename=filename,
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" if filename.endswith(".xlsx") else "text/csv"
    )
