from __future__ import annotations

import smtplib
from dataclasses import asdict, dataclass
from email.message import EmailMessage
from pathlib import Path
from typing import Any

from sqlalchemy.orm import Session

from app.core.secret_box import decrypt_secret
from app.models.system_setting import SystemSetting
from app.models.system_setting_secret import SystemSettingSecret
from app.services.finance_report_tenant import (
    TenantFinanceReportConfigService,
    require_report_tenant_id,
)
from app.services.settings import SettingsService


@dataclass(frozen=True, slots=True)
class FinanceReportDeliveryResult:
    status: str
    recipients: list[str]
    subject: str
    message: str
    smtp_host: str
    attachment_name: str

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


class FinanceReportMailer:
    def __init__(self, db: Session) -> None:
        self.db = db

    def send_report(
        self,
        *,
        context: dict[str, Any],
        pdf_path: Path,
        tenant_id: str,
        recipients: list[str] | None = None,
        dry_run: bool = False,
    ) -> FinanceReportDeliveryResult:
        settings_row, secrets_row = SettingsService(self.db).ensure_settings_ready()
        tenant = require_report_tenant_id(tenant_id)
        context_tenant = require_report_tenant_id(context.get("tenant_id"))
        if tenant != context_tenant:
            raise ValueError("报告上下文与投递租户不一致。")
        resolved_recipients = TenantFinanceReportConfigService(self.db).configured_recipients(
            tenant_id=tenant,
            requested=recipients,
        )
        subject = self._subject(context)

        missing = self._missing_config(settings_row, secrets_row, resolved_recipients)
        if missing:
            return FinanceReportDeliveryResult(
                status="pending_configuration",
                recipients=resolved_recipients,
                subject=subject,
                message=f"邮件未发送，缺少配置：{', '.join(missing)}。",
                smtp_host=str(settings_row.smtp_host or ""),
                attachment_name=pdf_path.name,
            )

        if dry_run:
            return FinanceReportDeliveryResult(
                status="dry_run",
                recipients=resolved_recipients,
                subject=subject,
                message="邮件 dry-run 完成，未连接 SMTP。",
                smtp_host=str(settings_row.smtp_host or ""),
                attachment_name=pdf_path.name,
            )

        password = self._decrypt_password(secrets_row)
        message = self._message(
            settings_row=settings_row,
            context=context,
            pdf_path=pdf_path,
            recipients=resolved_recipients,
            subject=subject,
        )
        try:
            self._send(settings_row, message, password)
        except Exception as exc:
            return FinanceReportDeliveryResult(
                status="failed",
                recipients=resolved_recipients,
                subject=subject,
                message=f"邮件发送失败：{exc}",
                smtp_host=str(settings_row.smtp_host or ""),
                attachment_name=pdf_path.name,
            )
        return FinanceReportDeliveryResult(
            status="sent",
            recipients=resolved_recipients,
            subject=subject,
            message="邮件已发送。",
            smtp_host=str(settings_row.smtp_host or ""),
            attachment_name=pdf_path.name,
        )

    def _message(
        self,
        *,
        settings_row: SystemSetting,
        context: dict[str, Any],
        pdf_path: Path,
        recipients: list[str],
        subject: str,
    ) -> EmailMessage:
        sender_address = str(
            settings_row.sender_address or settings_row.smtp_username or ""
        ).strip()
        sender_name = str(
            settings_row.sender_name or settings_row.company_name or "X-Financial"
        ).strip()
        summary = context.get("summary") if isinstance(context.get("summary"), dict) else {}
        insights = list(context.get("insights") or [])[:3]
        body = "\n".join(
            [
                "各位好，",
                "",
                "数字员工已生成本期财务经营报告，摘要如下：",
                *[f"- {item}" for item in insights],
                "",
                f"报销单数：{summary.get('reimbursement_count', 0)}",
                f"报销金额：¥{float(summary.get('reimbursement_amount') or 0):,.0f}",
                f"行动项：{summary.get('action_count', 0)}",
                "",
                "详细内容请查看附件 PDF。",
            ]
        )
        message = EmailMessage()
        message["Subject"] = subject
        message["From"] = f"{sender_name} <{sender_address}>"
        message["To"] = ", ".join(recipients)
        message.set_content(body)
        message.add_attachment(
            pdf_path.read_bytes(),
            maintype="application",
            subtype="pdf",
            filename=pdf_path.name,
        )
        return message

    @staticmethod
    def _missing_config(
        settings_row: SystemSetting,
        secrets_row: SystemSettingSecret,
        recipients: list[str],
    ) -> list[str]:
        missing: list[str] = []
        if not str(settings_row.smtp_host or "").strip():
            missing.append("smtp_host")
        if not int(settings_row.smtp_port or 0):
            missing.append("smtp_port")
        if not str(settings_row.sender_address or settings_row.smtp_username or "").strip():
            missing.append("sender_address")
        if not str(settings_row.smtp_username or "").strip():
            missing.append("smtp_username")
        if not str(secrets_row.smtp_password_encrypted or "").strip():
            missing.append("smtp_password")
        if not recipients:
            missing.append("tenant_report_recipients")
        return missing

    @staticmethod
    def _decrypt_password(secrets_row: SystemSettingSecret) -> str:
        encrypted = str(secrets_row.smtp_password_encrypted or "").strip()
        return decrypt_secret(encrypted) if encrypted else ""

    @staticmethod
    def _subject(context: dict[str, Any]) -> str:
        period = context.get("period") if isinstance(context.get("period"), dict) else {}
        title = str(period.get("title") or "财务经营报告")
        label = str(period.get("label") or "").strip()
        return f"X-Financial {title} | {label}".strip()

    @staticmethod
    def _send(settings_row: SystemSetting, message: EmailMessage, password: str) -> None:
        host = str(settings_row.smtp_host or "").strip()
        port = int(settings_row.smtp_port or 465)
        username = str(settings_row.smtp_username or "").strip()
        encryption = str(settings_row.smtp_encryption or "").strip().lower()
        if "ssl" in encryption:
            with smtplib.SMTP_SSL(host, port, timeout=20) as smtp:
                smtp.login(username, password)
                smtp.send_message(message)
            return
        with smtplib.SMTP(host, port, timeout=20) as smtp:
            if "tls" in encryption or "starttls" in encryption:
                smtp.starttls()
            smtp.login(username, password)
            smtp.send_message(message)
