import csv
import json
import logging
from datetime import datetime, timezone
from io import BytesIO, StringIO
from typing import TYPE_CHECKING

import yaml

import ckan.plugins.toolkit as tk

if TYPE_CHECKING:
    from ckanext.tables.table import ColumnDefinition, TableDefinition
    from ckanext.tables.types import QueryParams


log = logging.getLogger(__name__)


class ExporterBase:
    """Base class for table data exporters."""

    name: str
    label: str
    mime_type: str

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        """Export the table data.

        Args:
            table: The table definition.
            params: The query parameters.

        Returns:
            The exported data as bytes.
        """
        raise NotImplementedError

    @classmethod
    def get_table_columns(cls, table: "TableDefinition") -> list["ColumnDefinition"]:
        """Get the list of table columns to be exported.

        Returns:
            A list of column field names.
        """
        # avoid circular import
        from ckanext.tables.table import COLUMN_ACTIONS_FIELD  # noqa PLC0415

        return [col for col in table.columns if col.field != COLUMN_ACTIONS_FIELD]


class CSVExporter(ExporterBase):
    """CSV exporter for table data."""

    name = "csv"
    label = tk._("CSV")
    mime_type = "text/csv"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        output = StringIO()
        writer = csv.writer(output)
        columns = cls.get_table_columns(table)

        header = [col.title for col in columns]
        writer.writerow(header)

        # Write data rows
        data = table.get_raw_data(params, paginate=False)

        for row in data:
            writer.writerow([row.get(col.field, "") for col in columns])

        return output.getvalue().encode("utf-8")


class JSONExporter(ExporterBase):
    """JSON exporter for table data."""

    name = "json"
    label = tk._("JSON")
    mime_type = "application/json"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        data = table.get_raw_data(params, paginate=False)

        return json.dumps(data, default=str).encode("utf-8")


class XLSXExporter(ExporterBase):
    """Excel (XLSX) exporter for table data."""

    name = "xlsx"
    label = tk._("Excel")
    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        try:
            from openpyxl import Workbook  # noqa: PLC0415
        except ImportError:
            log.warning("openpyxl is required for XLSX export but is not installed.")
            return b""

        wb = Workbook()
        ws = wb.active
        ws.title = "Data"  # type: ignore

        columns = cls.get_table_columns(table)
        header = [col.title for col in columns]
        ws.append(header)  # type: ignore

        # Write data rows
        data = table.get_raw_data(params, paginate=False)
        for row in data:
            ws.append([row.get(col.field, "") for col in columns])  # type: ignore

        output = BytesIO()
        wb.save(output)
        return output.getvalue()


class TSVExporter(ExporterBase):
    """TSV exporter for table data."""

    name = "tsv"
    label = tk._("TSV")
    mime_type = "text/tab-separated-values"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        output = StringIO()
        writer = csv.writer(output, delimiter="\t")

        columns = cls.get_table_columns(table)
        header = [col.title for col in columns]
        writer.writerow(header)

        # Rows
        data = table.get_raw_data(params, paginate=False)
        for row in data:
            writer.writerow([row.get(col.field, "") for col in columns])

        return output.getvalue().encode("utf-8")


class YAMLExporter(ExporterBase):
    """YAML exporter for table data."""

    name = "yaml"
    label = tk._("YAML")
    mime_type = "application/x-yaml"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        data = table.get_raw_data(params, paginate=False)
        return yaml.safe_dump(data, allow_unicode=True).encode("utf-8")


class NDJSONExporter(ExporterBase):
    """NDJSON exporter for table data."""

    name = "ndjson"
    label = tk._("NDJSON")
    mime_type = "application/x-ndjson"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        data = table.get_raw_data(params, paginate=False)
        lines = [json.dumps(row, default=str) for row in data]
        return "\n".join(lines).encode("utf-8")


class HTMLExporter(ExporterBase):
    """HTML exporter for table data."""

    name = "html"
    label = tk._("HTML")
    mime_type = "text/html"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        data = table.get_raw_data(params, paginate=False)
        columns = cls.get_table_columns(table)
        headers = [col.title for col in columns]
        timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")

        return tk.render(
            "tables/exporters/html_export.html",
            {
                "data": data,
                "headers": headers,
                "table": table,
                "timestamp": timestamp,
                "columns": columns,
            },
        ).encode("utf-8")


class PDFExporter(ExporterBase):
    """PDF exporter for table data."""

    name = "pdf"
    label = tk._("PDF")
    mime_type = "application/pdf"

    @classmethod
    def export(cls, table: "TableDefinition", params: "QueryParams") -> bytes:
        try:
            from weasyprint import HTML  # noqa: PLC0415
        except ImportError:
            log.warning("WeasyPrint is required for PDF export but is not installed.")
            return b""

        # reuse HTML exporter template for PDF generation
        html_content = HTMLExporter.export(table, params).decode("utf-8")
        return HTML(string=html_content).write_pdf() or b""
