# app/src/trading/journal/exporter.py

from __future__ import annotations

import csv
import json
import re
import zipfile
from datetime import datetime
from io import BytesIO, StringIO
from xml.sax.saxutils import escape
from zoneinfo import ZoneInfo

from src.core.config import load_settings
from src.core.event_titles import event_title


_EMOJI_RE = re.compile(
    "["
    "\U0001F300-\U0001FAFF"
    "\U00002700-\U000027BF"
    "\U00002600-\U000026FF"
    "\U0001F1E6-\U0001F1FF"
    "]+",
    flags=re.UNICODE,
)


def _strip_emoji(value: object) -> str:
    text = str(value or "")
    return _EMOJI_RE.sub("", text).strip()


def _now_local() -> datetime:
    settings = load_settings()
    try:
        return datetime.now(ZoneInfo(settings.tz))
    except Exception:
        return datetime.utcnow()


def _format_datetime(value: object) -> str:
    text = str(value or "").strip()
    if not text:
        return ""

    try:
        settings = load_settings()
        dt = datetime.fromisoformat(text)

        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=ZoneInfo("UTC"))

        return dt.astimezone(ZoneInfo(settings.tz)).strftime("%Y-%m-%d %H:%M:%S")
    except Exception:
        return text


def _event_title(event_type: object) -> str:
    return event_title(event_type)


def _payload(row: dict[str, object]) -> dict[str, object]:
    payload = row.get("payload")
    return payload if isinstance(payload, dict) else {}


def _payload_json(payload: dict[str, object]) -> str:
    if not payload:
        return ""

    text = json.dumps(payload, ensure_ascii=False, sort_keys=True)
    return _strip_emoji(text)


def _export_row(row: dict[str, object]) -> list[str]:
    payload = _payload(row)

    return [
        _format_datetime(row.get("created_at")),
        _strip_emoji(row.get("level")),
        _strip_emoji(row.get("event_type")),
        _strip_emoji(_event_title(row.get("event_type"))),
        _strip_emoji(row.get("message")),
        _strip_emoji(str(payload.get("account_mode") or "").upper()),
        _strip_emoji(payload.get("screen")),
        _strip_emoji(payload.get("action")),
        _strip_emoji(payload.get("error_type")),
        _strip_emoji(payload.get("raw_error")),
        _payload_json(payload),
    ]


def _headers() -> list[str]:
    return [
        "Дата",
        "Уровень",
        "Событие",
        "Заголовок",
        "Сообщение",
        "Аккаунт",
        "Экран",
        "Действие",
        "Тип ошибки",
        "Техническая ошибка",
        "Payload",
    ]


def _levels_summary(rows: list[dict[str, object]]) -> str:
    levels = sorted(
        {str(row.get("level") or "").upper() for row in rows if row.get("level")}
    )
    return ", ".join(levels) if levels else "—"


def _period_summary(rows: list[dict[str, object]]) -> str:
    dates = [
        _format_datetime(row.get("created_at"))
        for row in rows
        if row.get("created_at")
    ]
    dates = [value for value in dates if value]

    if not dates:
        return "—"

    return f"{min(dates)} — {max(dates)}"


def _metadata_rows(
    *,
    rows: list[dict[str, object]],
    total_count: int,
    export_limit: int,
    account_mode: str,
    journal_level: str,
    export_filter_label: str = "Всё",
) -> list[list[str]]:
    exported_count = len(rows)
    is_limited = total_count > exported_count

    return [
        ["Экспорт журнала"],
        ["Дата экспорта", _now_local().strftime("%Y-%m-%d %H:%M:%S")],
        ["Аккаунт", account_mode.upper()],
        ["Фильтр", export_filter_label],
        ["Уровень журнала", journal_level],
        ["Всего записей в журнале", str(total_count)],
        ["Записей в файле", str(exported_count)],
        ["Лимит экспорта", str(export_limit)],
        ["Экспорт ограничен", "да" if is_limited else "нет"],
        ["Уровни в файле", _levels_summary(rows)],
        ["Период", _period_summary(rows)],
        [],
    ]


def build_csv(
    rows: list[dict[str, object]],
    *,
    total_count: int,
    export_limit: int,
    account_mode: str,
    journal_level: str,
    export_filter_label: str = "Всё",
) -> bytes:
    output = StringIO()
    writer = csv.writer(
        output,
        delimiter=";",
        quoting=csv.QUOTE_ALL,
        lineterminator="\n",
    )

    for metadata_row in _metadata_rows(
        rows=rows,
        total_count=total_count,
        export_limit=export_limit,
        account_mode=account_mode,
        journal_level=journal_level,
        export_filter_label=export_filter_label,
    ):
        writer.writerow(metadata_row)

    writer.writerow(_headers())

    for row in rows:
        writer.writerow(_export_row(row))

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


def build_xlsx(
    rows: list[dict[str, object]],
    *,
    total_count: int,
    export_limit: int,
    account_mode: str,
    journal_level: str,
    export_filter_label: str = "Всё",
) -> bytes:
    sheet_rows: list[list[str]] = []

    sheet_rows.extend(
        _metadata_rows(
            rows=rows,
            total_count=total_count,
            export_limit=export_limit,
            account_mode=account_mode,
            journal_level=journal_level,
            export_filter_label=export_filter_label,
        )
    )

    sheet_rows.append(_headers())

    for row in rows:
        sheet_rows.append(_export_row(row))

    return _build_xlsx_bytes(
        sheet_name="Journal",
        rows=sheet_rows,
    )


def _build_xlsx_bytes(
    *,
    sheet_name: str,
    rows: list[list[str]],
) -> bytes:
    stream = BytesIO()

    with zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED) as archive:
        archive.writestr("[Content_Types].xml", _content_types_xml())
        archive.writestr("_rels/.rels", _root_rels_xml())
        archive.writestr("xl/workbook.xml", _workbook_xml(sheet_name))
        archive.writestr("xl/_rels/workbook.xml.rels", _workbook_rels_xml())
        archive.writestr("xl/styles.xml", _styles_xml())
        archive.writestr("xl/worksheets/sheet1.xml", _worksheet_xml(rows))

    return stream.getvalue()


def _worksheet_xml(rows: list[list[str]]) -> str:
    header_row_index = _header_row_index(rows)
    column_widths = _column_widths(rows)

    xml_rows: list[str] = []

    for row_index, row in enumerate(rows, start=1):
        cells: list[str] = []

        for column_index, value in enumerate(row, start=1):
            cell_ref = f"{_column_letter(column_index)}{row_index}"
            style = ' s="1"' if row_index in {1, header_row_index} else ""

            cells.append(
                f'<c r="{cell_ref}" t="inlineStr"{style}>'
                f"<is><t>{_xml_text(value)}</t></is>"
                f"</c>"
            )

        xml_rows.append(f'<row r="{row_index}">{"".join(cells)}</row>')

    cols_xml = "".join(
        (
            f'<col min="{index}" max="{index}" '
            f'width="{width}" customWidth="1"/>'
        )
        for index, width in enumerate(column_widths, start=1)
    )

    return (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
        f"<cols>{cols_xml}</cols>"
        "<sheetData>"
        f"{''.join(xml_rows)}"
        "</sheetData>"
        "</worksheet>"
    )


def _header_row_index(rows: list[list[str]]) -> int:
    headers = _headers()

    for index, row in enumerate(rows, start=1):
        if row == headers:
            return index

    return 1


def _column_widths(rows: list[list[str]]) -> list[int]:
    max_columns = max((len(row) for row in rows), default=1)
    widths: list[int] = []

    for column_index in range(max_columns):
        max_length = 0

        for row in rows:
            if column_index < len(row):
                max_length = max(max_length, len(str(row[column_index] or "")))

        widths.append(min(max_length + 2, 60))

    return widths


def _column_letter(index: int) -> str:
    result = ""

    while index > 0:
        index, remainder = divmod(index - 1, 26)
        result = chr(65 + remainder) + result

    return result


def _xml_text(value: object) -> str:
    text = str(value or "")
    return escape(text, {'"': "&quot;", "'": "&apos;"})


def _content_types_xml() -> str:
    return (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
        '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
        '<Default Extension="xml" ContentType="application/xml"/>'
        '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
        '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
        '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'
        "</Types>"
    )


def _root_rels_xml() -> str:
    return (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
        '<Relationship Id="rId1" '
        'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" '
        'Target="xl/workbook.xml"/>'
        "</Relationships>"
    )


def _workbook_xml(sheet_name: str) -> str:
    return (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
        'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
        "<sheets>"
        f'<sheet name="{_xml_text(sheet_name)}" sheetId="1" r:id="rId1"/>'
        "</sheets>"
        "</workbook>"
    )


def _workbook_rels_xml() -> str:
    return (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
        '<Relationship Id="rId1" '
        'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" '
        'Target="worksheets/sheet1.xml"/>'
        '<Relationship Id="rId2" '
        'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" '
        'Target="styles.xml"/>'
        "</Relationships>"
    )


def _styles_xml() -> str:
    return (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
        "<fonts count=\"2\">"
        "<font><sz val=\"11\"/><name val=\"Calibri\"/></font>"
        "<font><b/><sz val=\"11\"/><name val=\"Calibri\"/></font>"
        "</fonts>"
        "<fills count=\"1\"><fill><patternFill patternType=\"none\"/></fill></fills>"
        "<borders count=\"1\"><border/></borders>"
        "<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>"
        "<cellXfs count=\"2\">"
        "<xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>"
        "<xf numFmtId=\"0\" fontId=\"1\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyFont=\"1\"/>"
        "</cellXfs>"
        "</styleSheet>"
    )