"""Excel export utilities for reports and data export"""

import io
import logging
from datetime import datetime

from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter

from app.utils.timezone import convert_app_datetime_to_user

logger = logging.getLogger(__name__)


def _safe_user_dt_iso(dt):
    """Convert app datetime to user datetime and return ISO string."""
    if not dt:
        return ""
    try:
        return convert_app_datetime_to_user(dt).isoformat()
    except Exception:
        # Fallback: keep export working even if timezone conversion fails
        try:
            return dt.isoformat()
        except Exception:
            return ""


def _safe_user_date_iso(dt):
    """Convert app datetime to user date and return YYYY-MM-DD."""
    if not dt:
        return ""
    try:
        return convert_app_datetime_to_user(dt).date().isoformat()
    except Exception:
        try:
            return dt.date().isoformat()
        except Exception:
            return ""


# Allowed columns for detailed time-entry export (used by reports/user/export/entries/excel)
# Each key maps to: (header label, extractor(entry) -> cell value)
ALLOWED_TIME_ENTRY_EXPORT_COLUMNS = {
    "id": ("ID", lambda e: e.id),
    "date": ("Date", lambda e: _safe_user_date_iso(getattr(e, "start_time", None))),
    "user": ("User", lambda e: e.user.display_name if getattr(e, "user", None) else "Unknown"),
    "project": ("Project", lambda e: e.project.name if getattr(e, "project", None) else "N/A"),
    "client": (
        "Client",
        lambda e: (
            (e.client.name if getattr(e, "client", None) else None)
            or (e.project.client if (getattr(e, "project", None) and getattr(e.project, "client", None)) else None)
            or "N/A"
        ),
    ),
    "billed": ("Billed", lambda e: "Yes" if getattr(e, "paid", False) else "No"),
    "task": ("Task", lambda e: e.task.name if getattr(e, "task", None) else "N/A"),
    "start_time": ("Start Time", lambda e: _safe_user_dt_iso(getattr(e, "start_time", None))),
    "end_time": ("End Time", lambda e: _safe_user_dt_iso(getattr(e, "end_time", None))),
    # Aliases: "duration" defaults to numeric hours (good for finance)
    "duration": ("Duration (hours)", lambda e: e.duration_hours if getattr(e, "end_time", None) else 0),
    "duration_hours": ("Duration (hours)", lambda e: e.duration_hours if getattr(e, "end_time", None) else 0),
    "duration_formatted": (
        "Duration (formatted)",
        lambda e: (e.duration_formatted if getattr(e, "end_time", None) else "In Progress"),
    ),
    "notes": ("Notes", lambda e: getattr(e, "notes", None) or ""),
    "tags": ("Tags", lambda e: getattr(e, "tags", None) or ""),
    "source": ("Source", lambda e: getattr(e, "source", None) or "manual"),
    "billable": ("Billable", lambda e: "Yes" if getattr(e, "billable", False) else "No"),
    "created_at": ("Created At", lambda e: _safe_user_dt_iso(getattr(e, "created_at", None))),
}


def create_time_entries_excel(entries, filename_prefix="timetracker_export", columns=None):
    """Create Excel file from time entries

    Args:
        entries: List of TimeEntry objects
        filename_prefix: Prefix for the filename
        columns: Optional list of column keys to export (see ALLOWED_TIME_ENTRY_EXPORT_COLUMNS).
                 If omitted/None, uses the legacy fixed export format.

    Returns:
        tuple: (BytesIO object with Excel file, filename)
    """
    # Create workbook
    wb = Workbook()
    ws = wb.active
    ws.title = "Time Entries"

    # Define styles
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center")
    border = Border(
        left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin")
    )

    # Determine columns/headers
    if columns is None:
        # Legacy fixed format (kept for backward compatibility)
        column_keys = [
            "id",
            "user",
            "project",
            "client",
            "task",
            "start_time",
            "end_time",
            "duration_hours",
            "duration_formatted",
            "notes",
            "tags",
            "source",
            "billable",
            "created_at",
        ]
    else:
        # Custom format: accept only known columns; preserve requested order
        column_keys = [c for c in columns if c in ALLOWED_TIME_ENTRY_EXPORT_COLUMNS]
        if not column_keys:
            column_keys = ["date", "user", "project", "task", "duration_hours", "notes"]

    headers = [ALLOWED_TIME_ENTRY_EXPORT_COLUMNS[k][0] for k in column_keys]

    # Write headers with styling
    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=1, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = header_alignment
        cell.border = border

    # Write data
    for row_num, entry in enumerate(entries, 2):
        data = []
        for key in column_keys:
            try:
                extractor = ALLOWED_TIME_ENTRY_EXPORT_COLUMNS[key][1]
                data.append(extractor(entry))
            except Exception as e:
                logger.debug(f"Error exporting column {key}: {e}")
                data.append("")

        for col_num, value in enumerate(data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=value)
            cell.border = border

            # Format duration columns as numbers
            if column_keys[col_num - 1] in {"duration", "duration_hours"} and isinstance(value, (int, float)):
                cell.number_format = "0.00"

    # Auto-adjust column widths
    for col_idx, col in enumerate(ws.columns, 1):
        max_length = 0
        # Get column letter - use column index (1-based) to avoid MergedCell issues
        column = get_column_letter(col_idx)
        for cell in col:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except (AttributeError, TypeError) as e:
                # Cell value may be None or not have expected attributes
                logger.debug(f"Error reading cell value: {e}")
                pass
        adjusted_width = min(max_length + 2, 50)  # Cap at 50
        ws.column_dimensions[column].width = adjusted_width

    # Add summary at the bottom only for legacy exports
    if columns is None:
        last_row = len(entries) + 2
        ws.cell(row=last_row + 1, column=1, value="Summary")
        ws.cell(row=last_row + 1, column=1).font = Font(bold=True)

        total_hours = sum(e.duration_hours for e in entries if getattr(e, "end_time", None))
        billable_hours = sum(
            e.duration_hours for e in entries if getattr(e, "end_time", None) and getattr(e, "billable", False)
        )

        ws.cell(row=last_row + 2, column=1, value="Total Hours:")
        ws.cell(row=last_row + 2, column=2, value=total_hours).number_format = "0.00"
        ws.cell(row=last_row + 3, column=1, value="Billable Hours:")
        ws.cell(row=last_row + 3, column=2, value=billable_hours).number_format = "0.00"
        ws.cell(row=last_row + 4, column=1, value="Total Entries:")
        ws.cell(row=last_row + 4, column=2, value=len(entries))

    # Save to BytesIO
    output = io.BytesIO()
    wb.save(output)
    output.seek(0)

    # Generate filename
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"{filename_prefix}_{timestamp}.xlsx"

    return output, filename


def create_project_report_excel(projects_data, start_date, end_date):
    """Create Excel file for project report

    Args:
        projects_data: List of project dictionaries with hours and costs
        start_date: Report start date
        end_date: Report end date

    Returns:
        tuple: (BytesIO object with Excel file, filename)
    """
    wb = Workbook()
    ws = wb.active
    ws.title = "Project Report"

    # Styles
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    border = Border(
        left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin")
    )

    # Add report header
    ws.merge_cells("A1:H1")
    title_cell = ws["A1"]
    title_cell.value = f"Project Report: {start_date} to {end_date}"
    title_cell.font = Font(bold=True, size=14)
    title_cell.alignment = Alignment(horizontal="center")

    # Column headers
    headers = [
        "Project",
        "Client",
        "Total Hours",
        "Billable Hours",
        "Hourly Rate",
        "Billable Amount",
        "Total Costs",
        "Total Value",
    ]

    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=3, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.border = border

    # Write project data
    for row_num, project in enumerate(projects_data, 4):
        data = [
            project.get("name", ""),
            project.get("client", ""),
            project.get("total_hours", 0),
            project.get("billable_hours", 0),
            project.get("hourly_rate", 0),
            project.get("billable_amount", 0),
            project.get("total_costs", 0),
            project.get("total_value", 0),
        ]

        for col_num, value in enumerate(data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=value)
            cell.border = border

            # Format numbers
            if col_num in [3, 4]:  # Hours
                cell.number_format = "0.00"
            elif col_num in [5, 6, 7, 8]:  # Money
                cell.number_format = "#,##0.00"

    # Auto-adjust columns
    for col_idx, col in enumerate(ws.columns, 1):
        max_length = 0
        # Get column letter - use column index (1-based) to avoid MergedCell issues
        # MergedCell objects don't have column_letter attribute
        column = get_column_letter(col_idx)
        for cell in col:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except (AttributeError, TypeError) as e:
                # Cell value may be None or not have expected attributes
                logger.debug(f"Error reading cell value: {e}")
                pass
        adjusted_width = min(max_length + 2, 40)
        ws.column_dimensions[column].width = adjusted_width

    # Add totals
    last_row = len(projects_data) + 4
    ws.cell(row=last_row + 1, column=1, value="TOTALS").font = Font(bold=True)

    total_hours = sum(p.get("total_hours", 0) for p in projects_data)
    total_billable_hours = sum(p.get("billable_hours", 0) for p in projects_data)
    total_amount = sum(p.get("billable_amount", 0) for p in projects_data)
    total_costs = sum(p.get("total_costs", 0) for p in projects_data)
    total_value = sum(p.get("total_value", 0) for p in projects_data)

    ws.cell(row=last_row + 1, column=3, value=total_hours).number_format = "0.00"
    ws.cell(row=last_row + 1, column=4, value=total_billable_hours).number_format = "0.00"
    ws.cell(row=last_row + 1, column=6, value=total_amount).number_format = "#,##0.00"
    ws.cell(row=last_row + 1, column=7, value=total_costs).number_format = "#,##0.00"
    ws.cell(row=last_row + 1, column=8, value=total_value).number_format = "#,##0.00"

    # Save to BytesIO
    output = io.BytesIO()
    wb.save(output)
    output.seek(0)

    filename = f"project_report_{start_date}_to_{end_date}.xlsx"
    return output, filename


def create_invoice_excel(invoice, items):
    """Create Excel file for a single invoice

    Args:
        invoice: Invoice object
        items: List of InvoiceItem objects

    Returns:
        tuple: (BytesIO object with Excel file, filename)
    """
    wb = Workbook()
    ws = wb.active
    ws.title = "Invoice"

    # Invoice header
    ws.merge_cells("A1:D1")
    ws["A1"] = f"INVOICE {invoice.invoice_number}"
    ws["A1"].font = Font(bold=True, size=16)
    ws["A1"].alignment = Alignment(horizontal="center")

    # Invoice details
    ws["A3"] = "Client:"
    ws["B3"] = invoice.client_name
    ws["A4"] = "Issue Date:"
    ws["B4"] = invoice.issue_date.strftime("%Y-%m-%d")
    ws["A5"] = "Due Date:"
    ws["B5"] = invoice.due_date.strftime("%Y-%m-%d")
    ws["A6"] = "Status:"
    ws["B6"] = invoice.status.upper()

    # Items header
    headers = ["Description", "Quantity", "Unit Price", "Amount"]
    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=8, column=col_num, value=header)
        cell.font = Font(bold=True)
        cell.fill = PatternFill(start_color="E7E6E6", end_color="E7E6E6", fill_type="solid")

    # Items
    row = 9
    for item in items:
        ws.cell(row=row, column=1, value=item.description)
        ws.cell(row=row, column=2, value=item.quantity).number_format = "0.00"
        ws.cell(row=row, column=3, value=float(item.unit_price)).number_format = "#,##0.00"
        ws.cell(row=row, column=4, value=float(item.amount)).number_format = "#,##0.00"
        row += 1

    # Totals
    row += 1
    ws.cell(row=row, column=3, value="Subtotal:").font = Font(bold=True)
    ws.cell(row=row, column=4, value=float(invoice.subtotal)).number_format = "#,##0.00"

    row += 1
    ws.cell(row=row, column=3, value=f"Tax ({invoice.tax_rate}%):").font = Font(bold=True)
    ws.cell(row=row, column=4, value=float(invoice.tax_amount)).number_format = "#,##0.00"

    row += 1
    ws.cell(row=row, column=3, value="TOTAL:").font = Font(bold=True, size=12)
    total_cell = ws.cell(row=row, column=4, value=float(invoice.total_amount))
    total_cell.number_format = "#,##0.00"
    total_cell.font = Font(bold=True, size=12)
    total_cell.fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")

    # Adjust columns
    ws.column_dimensions["A"].width = 40
    ws.column_dimensions["B"].width = 15
    ws.column_dimensions["C"].width = 15
    ws.column_dimensions["D"].width = 15

    # Save
    output = io.BytesIO()
    wb.save(output)
    output.seek(0)

    filename = f"{invoice.invoice_number}.xlsx"
    return output, filename


def create_invoices_list_excel(invoices):
    """Create Excel file for invoice list

    Args:
        invoices: List of Invoice objects

    Returns:
        tuple: (BytesIO object with Excel file, filename)
    """
    wb = Workbook()
    ws = wb.active
    ws.title = "Invoices"

    # Define styles
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center")
    border = Border(
        left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin")
    )

    # Headers
    headers = [
        "Invoice Number",
        "Client Name",
        "Project",
        "Issue Date",
        "Due Date",
        "Status",
        "Payment Status",
        "Subtotal",
        "Tax Rate (%)",
        "Tax Amount",
        "Total Amount",
        "Amount Paid",
        "Outstanding",
        "Currency",
        "Created By",
        "Created At",
    ]

    # Write headers with styling
    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=1, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = header_alignment
        cell.border = border

    # Write data
    for row_num, invoice in enumerate(invoices, 2):
        data = [
            invoice.invoice_number,
            invoice.client_name or "N/A",
            invoice.project.name if invoice.project else "N/A",
            invoice.issue_date.strftime("%Y-%m-%d") if invoice.issue_date else "",
            invoice.due_date.strftime("%Y-%m-%d") if invoice.due_date else "",
            invoice.status or "draft",
            invoice.payment_status or "unpaid",
            float(invoice.subtotal or 0),
            float(invoice.tax_rate or 0),
            float(invoice.tax_amount or 0),
            float(invoice.total_amount or 0),
            float(invoice.amount_paid or 0),
            float(invoice.outstanding_amount or 0),
            invoice.currency_code or "USD",
            invoice.creator.display_name if invoice.creator else "Unknown",
            (convert_app_datetime_to_user(invoice.created_at).strftime("%Y-%m-%d %H:%M") if invoice.created_at else ""),
        ]

        for col_num, value in enumerate(data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=value)
            cell.border = border

            # Format number columns
            if col_num in [8, 10, 11, 12, 13]:  # Money columns
                if isinstance(value, (int, float)):
                    cell.number_format = "#,##0.00"
            elif col_num == 9:  # Tax rate percentage
                if isinstance(value, (int, float)):
                    cell.number_format = "0.00"

    # Auto-adjust column widths
    for col_idx, col in enumerate(ws.columns, 1):
        max_length = 0
        # Get column letter - use column index (1-based) to avoid MergedCell issues
        column = get_column_letter(col_idx)
        for cell in col:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except (AttributeError, TypeError) as e:
                # Cell value may be None or not have expected attributes
                logger.debug(f"Error reading cell value: {e}")
                pass
        adjusted_width = min(max_length + 2, 50)  # Cap at 50
        ws.column_dimensions[column].width = adjusted_width

    # Add summary at the bottom
    last_row = len(invoices) + 2
    ws.cell(row=last_row + 1, column=1, value="Summary")
    ws.cell(row=last_row + 1, column=1).font = Font(bold=True)

    total_invoiced = sum(float(inv.total_amount or 0) for inv in invoices)
    total_paid = sum(float(inv.amount_paid or 0) for inv in invoices)
    total_outstanding = sum(float(inv.outstanding_amount or 0) for inv in invoices)

    ws.cell(row=last_row + 2, column=1, value="Total Invoiced:")
    ws.cell(row=last_row + 2, column=2, value=total_invoiced).number_format = "#,##0.00"
    ws.cell(row=last_row + 3, column=1, value="Total Paid:")
    ws.cell(row=last_row + 3, column=2, value=total_paid).number_format = "#,##0.00"
    ws.cell(row=last_row + 4, column=1, value="Total Outstanding:")
    ws.cell(row=last_row + 4, column=2, value=total_outstanding).number_format = "#,##0.00"
    ws.cell(row=last_row + 5, column=1, value="Total Invoices:")
    ws.cell(row=last_row + 5, column=2, value=len(invoices))

    # Save to BytesIO
    output = io.BytesIO()
    wb.save(output)
    output.seek(0)

    # Generate filename
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"invoices_list_{timestamp}.xlsx"

    return output, filename


def create_payments_list_excel(payments):
    """Create Excel file for payment list

    Args:
        payments: List of Payment objects

    Returns:
        tuple: (BytesIO object with Excel file, filename)
    """
    wb = Workbook()
    ws = wb.active
    ws.title = "Payments"

    # Define styles
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center")
    border = Border(
        left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin")
    )

    # Headers
    headers = [
        "Payment ID",
        "Invoice Number",
        "Client Name",
        "Amount",
        "Currency",
        "Gateway Fee",
        "Net Amount",
        "Payment Date",
        "Method",
        "Reference",
        "Status",
        "Received By",
        "Gateway Transaction ID",
        "Notes",
        "Created At",
    ]

    # Write headers with styling
    for col_num, header in enumerate(headers, 1):
        cell = ws.cell(row=1, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = header_alignment
        cell.border = border

    # Write data
    for row_num, payment in enumerate(payments, 2):
        data = [
            payment.id,
            payment.invoice.invoice_number if payment.invoice else "N/A",
            payment.invoice.client_name if payment.invoice else "N/A",
            float(payment.amount or 0),
            payment.currency or "EUR",
            float(payment.gateway_fee or 0),
            float(payment.net_amount or payment.amount or 0),
            payment.payment_date.strftime("%Y-%m-%d") if payment.payment_date else "",
            payment.method or "N/A",
            payment.reference or "",
            payment.status or "completed",
            payment.receiver.display_name if payment.receiver else "N/A",
            payment.gateway_transaction_id or "",
            payment.notes or "",
            (convert_app_datetime_to_user(payment.created_at).strftime("%Y-%m-%d %H:%M") if payment.created_at else ""),
        ]

        for col_num, value in enumerate(data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=value)
            cell.border = border

            # Format number columns
            if col_num in [4, 6, 7]:  # Money columns
                if isinstance(value, (int, float)):
                    cell.number_format = "#,##0.00"

    # Auto-adjust column widths
    for col_idx, col in enumerate(ws.columns, 1):
        max_length = 0
        # Get column letter - use column index (1-based) to avoid MergedCell issues
        column = get_column_letter(col_idx)
        for cell in col:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except (AttributeError, TypeError) as e:
                # Cell value may be None or not have expected attributes
                logger.debug(f"Error reading cell value: {e}")
                pass
        adjusted_width = min(max_length + 2, 50)  # Cap at 50
        ws.column_dimensions[column].width = adjusted_width

    # Add summary at the bottom
    last_row = len(payments) + 2
    ws.cell(row=last_row + 1, column=1, value="Summary")
    ws.cell(row=last_row + 1, column=1).font = Font(bold=True)

    total_amount = sum(float(p.amount or 0) for p in payments)
    total_fees = sum(float(p.gateway_fee or 0) for p in payments if p.gateway_fee)
    total_net = sum(float(p.net_amount or p.amount or 0) for p in payments)
    completed_count = sum(1 for p in payments if p.status == "completed")

    ws.cell(row=last_row + 2, column=1, value="Total Amount:")
    ws.cell(row=last_row + 2, column=2, value=total_amount).number_format = "#,##0.00"
    ws.cell(row=last_row + 3, column=1, value="Total Gateway Fees:")
    ws.cell(row=last_row + 3, column=2, value=total_fees).number_format = "#,##0.00"
    ws.cell(row=last_row + 4, column=1, value="Total Net Amount:")
    ws.cell(row=last_row + 4, column=2, value=total_net).number_format = "#,##0.00"
    ws.cell(row=last_row + 5, column=1, value="Total Payments:")
    ws.cell(row=last_row + 5, column=2, value=len(payments))
    ws.cell(row=last_row + 6, column=1, value="Completed Payments:")
    ws.cell(row=last_row + 6, column=2, value=completed_count)

    # Save to BytesIO
    output = io.BytesIO()
    wb.save(output)
    output.seek(0)

    # Generate filename
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"payments_list_{timestamp}.xlsx"

    return output, filename
