"""Reports pane widget with multi-period financial reports (IS, BS, CF)."""

from __future__ import annotations

from datetime import date
from pathlib import Path

from textual import on, work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal
from textual.widget import Widget
from rich.text import Text
from textual.widgets import DataTable, Select

from hledger_textual.cache import HledgerCache
from hledger_textual.config import load_default_commodity
from hledger_textual.hledger import HledgerError, load_investment_report, load_report
from hledger_textual.models import ReportData, ReportRow
from hledger_textual.widgets import distribute_column_widths
from hledger_textual.widgets.formatting import fmt_amount_str
from hledger_textual.widgets.pane_mixin import DataTablePaneMixin
from hledger_textual.widgets.pane_toolbar import PaneToolbar
from hledger_textual.widgets.report_chart import ReportChart, extract_chart_data

_REPORT_TYPES = [
    ("Income Statement", "is"),
    ("Balance Sheet", "bs"),
    ("Cash Flow", "cf"),
]

_PERIOD_RANGES = [
    ("3 months", 3),
    ("6 months", 6),
    ("12 months", 12),
    ("Year to date", 0),
]


def _merge_investments(is_data: ReportData, inv_data: ReportData) -> ReportData:
    """Append investment rows as a new section to an Income Statement report.

    Args:
        is_data: The original Income Statement report data.
        inv_data: Investment balance report data.

    Returns:
        A new :class:`ReportData` with the investment rows appended.
    """
    merged_rows = list(is_data.rows)

    # Add "Investments" section header
    n_periods = len(is_data.period_headers)
    merged_rows.append(ReportRow(
        account="Investments",
        amounts=[""] * n_periods,
        is_section_header=True,
    ))

    # Add data rows only (skip section headers and totals): each leaf account
    # (XEON, XDWD, XGDU) is already its own row, so the Total row is redundant
    # and confusing when it concatenates multiple commodities in a single cell.
    # Strip the common "assets:investments:" prefix to show only the leaf name.
    for row in inv_data.rows:
        if not row.is_section_header and not row.is_total:
            account = row.account
            if ":" in account:
                account = account.rsplit(":", 1)[-1]
            merged_rows.append(ReportRow(
                account=account,
                amounts=row.amounts,
            ))

    return ReportData(
        title=is_data.title,
        period_headers=is_data.period_headers,
        rows=merged_rows,
    )


class ReportsPane(DataTablePaneMixin, Widget):
    """Widget showing multi-period hledger financial reports."""

    _main_table_id = "reports-table"

    BINDINGS = [
        Binding("r", "refresh", "Refresh", show=True, priority=True),
        Binding("c", "toggle_chart", "Chart", show=False, priority=True),
        Binding("i", "toggle_investments", "Investments", show=False, priority=True),
        Binding("x", "export", "Export", show=False, priority=True),
        Binding("j", "cursor_down", "Down", show=False),
        Binding("k", "cursor_up", "Up", show=False),
    ]

    def __init__(self, journal_file: Path, cache: HledgerCache | None = None, **kwargs) -> None:
        """Initialize the pane.

        Args:
            journal_file: Path to the hledger journal file.
            cache: Optional cache instance to avoid repeated subprocess calls.
        """
        super().__init__(**kwargs)
        self.journal_file = journal_file
        self._cache = cache
        self._report_type: str = "is"
        self._period_months: int = 6
        self._report_data: ReportData | None = None
        self._fixed_widths: dict[int, int] = {}
        self._show_investments: bool = False

    def compose(self) -> ComposeResult:
        """Create the pane layout."""
        with PaneToolbar():
            yield Select(
                [(label, value) for label, value in _REPORT_TYPES],
                value="is",
                id="report-type-select",
                allow_blank=False,
            )
            yield Select(
                [(label, value) for label, value in _PERIOD_RANGES],
                value=6,
                id="report-period-select",
                allow_blank=False,
            )

        yield DataTable(id="reports-table")
        yield ReportChart(id="report-chart")

    def on_mount(self) -> None:
        """Set up the DataTable and load report data."""
        table = self.query_one("#reports-table", DataTable)
        table.cursor_type = "row"
        self._load_report_data()

    def on_resize(self) -> None:
        """Recalculate column widths when the pane is resized."""
        if self._fixed_widths:
            table = self.query_one("#reports-table", DataTable)
            distribute_column_widths(table, self._fixed_widths)

    def _period_range(self) -> tuple[str, str]:
        """Calculate begin and end dates based on the selected period range.

        Returns:
            A ``(begin, end)`` tuple of date strings in ``YYYY-MM-DD`` format.
        """
        today = date.today()
        # End: first day of next month
        if today.month == 12:
            end = date(today.year + 1, 1, 1)
        else:
            end = date(today.year, today.month + 1, 1)

        if self._period_months == 0:
            # Year to date
            begin = date(today.year, 1, 1)
        else:
            # Go back N months
            month = today.month - self._period_months + 1
            year = today.year
            while month < 1:
                month += 12
                year -= 1
            begin = date(year, month, 1)

        return begin.isoformat(), end.isoformat()

    @work(thread=True, exclusive=True, group="reports-load")
    def _load_report_data(self) -> None:
        """Load report data in a background thread."""
        begin, end = self._period_range()
        commodity = load_default_commodity()

        try:
            data = load_report(
                self.journal_file,
                self._report_type,
                period_begin=begin,
                period_end=end,
                commodity=commodity,
                cache=self._cache,
            )
        except HledgerError as exc:
            self.app.call_from_thread(
                self.notify, str(exc), severity="error", timeout=8
            )
            data = ReportData(title="", period_headers=[], rows=[])

        if self._show_investments and self._report_type == "is":
            try:
                inv_data = load_investment_report(
                    self.journal_file,
                    period_begin=begin,
                    period_end=end,
                    commodity=commodity,
                )
                if inv_data.rows:
                    data = _merge_investments(data, inv_data)
            except HledgerError:
                pass

        self._report_data = data
        self.app.call_from_thread(self._apply_report)

    def _apply_report(self) -> None:
        """Apply loaded report data to the DataTable."""
        table = self.query_one("#reports-table", DataTable)
        data = self._report_data

        if data is None:
            return

        # Rebuild columns
        table.clear(columns=True)

        table.add_column("Account", key="account")
        self._fixed_widths = {}
        for i, header in enumerate(data.period_headers):
            col_idx = i + 1
            table.add_column(header, key=f"period-{i}")
            self._fixed_widths[col_idx] = 18

        # Populate rows with blank separator lines between groups
        n_cols = len(data.period_headers) + 1
        empty_row = [""] * n_cols

        for idx, row in enumerate(data.rows):
            if row.is_section_header and idx > 0:
                table.add_row(*empty_row)
            elif row.is_total:
                table.add_row(*empty_row)

            if row.is_section_header:
                account_text = Text.from_markup(
                    f"[bold cyan]{row.account}[/bold cyan]", emoji=False
                )
            elif row.is_total:
                account_text = Text.from_markup(
                    f"[bold yellow]{row.account}[/bold yellow]", emoji=False
                )
            else:
                account_text = Text(row.account)

            cells = [account_text]
            for amt in row.amounts:
                formatted = fmt_amount_str(amt)
                if row.is_total:
                    cells.append(f"[bold]{formatted}[/bold]")
                else:
                    cells.append(formatted)

            # Pad if amounts are fewer than period columns
            while len(cells) < len(data.period_headers) + 1:
                cells.append("")

            table.add_row(*cells)

        # Distribute widths
        if self._fixed_widths:
            distribute_column_widths(table, self._fixed_widths)

        self._update_chart()

    def _update_chart(self) -> None:
        """Extract chart data from the current report and replot."""
        chart = self.query_one("#report-chart", ReportChart)
        chart_data = extract_chart_data(self._report_data, self._report_type)
        chart.replot(chart_data, self._report_type)

    # --- Actions ---

    def action_toggle_chart(self) -> None:
        """Toggle chart visibility."""
        chart = self.query_one("#report-chart", ReportChart)
        chart.toggle_class("visible")

    def action_toggle_investments(self) -> None:
        """Toggle the Investments section on the Income Statement."""
        self._show_investments = not self._show_investments
        label = "Investments shown" if self._show_investments else "Investments hidden"
        self.notify(label, timeout=2)
        self._load_report_data()

    def action_refresh(self) -> None:
        """Reload report data."""
        self._load_report_data()
        self.notify("Refreshed", timeout=2)

    # --- Event handlers ---

    @on(Select.Changed, "#report-type-select")
    def on_report_type_changed(self, event: Select.Changed) -> None:
        """Reload when the report type changes."""
        if event.value is not Select.BLANK:
            self._report_type = event.value
            self._load_report_data()

    def get_export_data(self):
        """Return an ExportData with report rows for export.

        Returns:
            An ExportData instance with Account and period columns.
        """
        from hledger_textual.export import ExportData

        data = self._report_data
        if data is None:
            return ExportData(
                title="Report",
                headers=["Account"],
                rows=[],
                pane_name="report",
            )

        headers = ["Account"] + list(data.period_headers)
        rows: list[list[str]] = []

        for row in data.rows:
            cells = [row.account]
            cells.extend(row.amounts)
            # Pad if amounts are fewer than period columns
            while len(cells) < len(headers):
                cells.append("")
            rows.append(cells)

        # Determine report type label
        type_labels = {"is": "Income Statement", "bs": "Balance Sheet", "cf": "Cash Flow"}
        type_label = type_labels.get(self._report_type, self._report_type)

        return ExportData(
            title=type_label,
            headers=headers,
            rows=rows,
            pane_name="report",
        )

    @on(Select.Changed, "#report-period-select")
    def on_period_range_changed(self, event: Select.Changed) -> None:
        """Reload when the period range changes."""
        if event.value is not Select.BLANK:
            self._period_months = event.value
            self._load_report_data()
