"""Correspondence service layer — all business logic lives here.

Rules
-----
* Views MUST NOT implement business logic; they call services.
* Services MUST NOT directly render HTTP responses.
* Services emit domain events via the event bus, never Django signals.
"""

from __future__ import annotations

from datetime import date
from typing import Any

import structlog
from django.db import transaction

_log = structlog.get_logger("simorgh.correspondence")


# ═══════════════════════════════════════════════════════════════════════════
# Numbering helpers
# ═══════════════════════════════════════════════════════════════════════════

def generate_registration_number(
    template: NumberingTemplate | None,
    *,
    tenant_id: int,
    org_node_id: int,
) -> str:
    """Generate a registration number from a numbering template.

    Pattern placeholders:
      {year}        → 2026
      {year_short}  → 26
      {month}       → 06
      {day}         → 13
      {counter}     → auto-incremented, formatted as per :04d etc.
      {org_code}    → organization node code (or org pk as fallback)
      {prefix}      → template prefix
      {tenant_id}   → tenant pk
    """
    import re


    today = date.today()
    if template is None:
        # Use default pattern from settings
        try:
            from simorgh.apps.app_settings.services import resolve

            pattern = resolve(
                "correspondence.default_numbering_pattern", tenant_id=tenant_id
            )
            prefix = resolve("correspondence.default_prefix", tenant_id=tenant_id)
        except Exception:
            pattern = "{year}/{counter:04d}"
            prefix = ""
        template_to_use = None
    else:
        pattern = template.pattern
        prefix = template.prefix
        template_to_use = template

    # Auto-increment the counter
    counter = 1
    if template_to_use:
        template_to_use.counter += 1
        template_to_use.save(update_fields=["counter"])
        counter = template_to_use.counter

    # Build context for replacements
    ctx = {
        "year": str(today.year),
        "year_short": str(today.year)[2:],
        "month": f"{today.month:02d}",
        "day": f"{today.day:02d}",
        "counter": counter,
        "prefix": prefix,
        "tenant_id": str(tenant_id),
        "org_code": str(org_node_id),
    }

    def _replace_counter(match: re.Match) -> str:
        """Handle {counter:04d} style formatting."""
        fmt = match.group(1)
        return f"{counter:{fmt}}"

    result = pattern
    # Handle formatted counter: {counter:04d}
    result = re.sub(r"\{counter:(\d+d)\}", _replace_counter, result)
    # Handle plain placeholders
    for key, val in ctx.items():
        result = result.replace("{" + key + "}", val)

    return result


# ═══════════════════════════════════════════════════════════════════════════
# LetterService
# ═══════════════════════════════════════════════════════════════════════════


class LetterService:
    """Central service for Letter lifecycle operations."""

    # ── Creation ──────────────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def create(
        *,
        tenant_id: int,
        organization_node_id: int,
        letter_type: str,
        subject: str,
        body: str = "",
        priority: str = "normal",
        security: str = "normal",
        status: str = "draft",
        sender_id: int | None = None,
        sender_external: str = "",
        sender_organization: str = "",
        category_id: int | None = None,
        template_id: int | None = None,
        numbering_template_id: int | None = None,
        letterhead_id: int | None = None,
        registration_date: date | None = None,
        letter_date: date | None = None,
        received_date: date | None = None,
        due_date: date | None = None,
        external_reference: str = "",
        summary: str = "",
        parent_id: int | None = None,
        tag_ids: list[int] | None = None,
        auto_number: bool = True,
        actor_id: int | None = None,
        metadata: dict | None = None,
    ) -> Letter:
        from simorgh.apps.correspondence.models import (
            Letter,
            LetterTemplate,
            NumberingTemplate,
        )
        from simorgh.apps.events.bus import dispatch

        # Resolve template defaults
        resolved_template = None
        if template_id:
            resolved_template = LetterTemplate.objects.filter(
                tenant_id=tenant_id, pk=template_id
            ).first()

        # Build the letter
        letter = Letter.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            letter_type=letter_type,
            status=status if status != "draft" else "draft",
            priority=priority,
            security=security,
            subject=subject,
            body=body,
            summary=summary,
            sender_id=sender_id,
            sender_external=sender_external,
            sender_organization=sender_organization,
            category_id=category_id,
            template_id=template_id,
            numbering_template_id=numbering_template_id,
            letterhead_id=letterhead_id,
            registration_date=registration_date or date.today(),
            letter_date=letter_date,
            received_date=received_date,
            due_date=due_date,
            external_reference=external_reference,
            parent_id=parent_id,
            metadata=metadata or {},
        )

        # Auto-number if enabled
        if auto_number and not letter.registration_number:
            try:
                from simorgh.apps.app_settings.services import resolve

                if resolve("correspondence.auto_numbering", tenant_id=tenant_id):
                    nt = None
                    if numbering_template_id:
                        nt = NumberingTemplate.objects.filter(
                            tenant_id=tenant_id, pk=numbering_template_id
                        ).first()
                    if nt is None and resolved_template and resolved_template.numbering_template:
                        nt = resolved_template.numbering_template
                    letter.registration_number = generate_registration_number(
                        nt,
                        tenant_id=tenant_id,
                        org_node_id=organization_node_id,
                    )
                    letter.save(update_fields=["registration_number"])
            except Exception:
                _log.warning(
                    "correspondence.services.create.auto_number_failed",
                    letter_id=letter.pk,
                )

        # Tags
        if tag_ids:
            from simorgh.apps.platform_core.services import add_tags

            add_tags(
                entity=letter,
                tag_ids=tag_ids,
            )

        # Dispatch event
        dispatch(
            "correspondence.letter.created",
            {
                "letter_id": letter.pk,
                "tenant_id": tenant_id,
                "org_node_id": organization_node_id,
                "letter_type": letter_type,
                "actor_id": actor_id,
            },
        )

        return letter

    # ── Register incoming letter ──────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def register_incoming(
        *,
        tenant_id: int,
        organization_node_id: int,
        subject: str,
        sender_external: str = "",
        sender_organization: str = "",
        body: str = "",
        priority: str = "normal",
        security: str = "normal",
        category_id: int | None = None,
        numbering_template_id: int | None = None,
        registration_date: date | None = None,
        received_date: date | None = None,
        letter_date: date | None = None,
        external_reference: str = "",
        summary: str = "",
        tag_ids: list[int] | None = None,
        actor_id: int | None = None,
        metadata: dict | None = None,
    ) -> Letter:
        from simorgh.apps.events.bus import dispatch

        letter = LetterService.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            letter_type="incoming",
            subject=subject,
            body=body,
            priority=priority,
            security=security,
            status="received",
            sender_external=sender_external,
            sender_organization=sender_organization,
            category_id=category_id,
            numbering_template_id=numbering_template_id,
            registration_date=registration_date or date.today(),
            received_date=received_date or date.today(),
            letter_date=letter_date,
            external_reference=external_reference,
            summary=summary,
            tag_ids=tag_ids,
            actor_id=actor_id,
            metadata=metadata,
        )

        dispatch(
            "correspondence.letter.received",
            {
                "letter_id": letter.pk,
                "tenant_id": tenant_id,
                "received_date": str(letter.received_date),
                "actor_id": actor_id,
            },
        )

        if letter.registration_number:
            dispatch(
                "correspondence.letter.registered",
                {
                    "letter_id": letter.pk,
                    "tenant_id": tenant_id,
                    "registration_number": letter.registration_number,
                    "actor_id": actor_id,
                },
            )

        return letter

    # ── Create outgoing letter ────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def create_outgoing(
        *,
        tenant_id: int,
        organization_node_id: int,
        subject: str,
        body: str = "",
        sender_id: int | None = None,
        priority: str = "normal",
        security: str = "normal",
        category_id: int | None = None,
        template_id: int | None = None,
        numbering_template_id: int | None = None,
        letterhead_id: int | None = None,
        registration_date: date | None = None,
        letter_date: date | None = None,
        due_date: date | None = None,
        external_reference: str = "",
        summary: str = "",
        tag_ids: list[int] | None = None,
        actor_id: int | None = None,
        metadata: dict | None = None,
    ) -> Letter:
        return LetterService.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            letter_type="outgoing",
            subject=subject,
            body=body,
            priority=priority,
            security=security,
            status="draft",
            sender_id=sender_id or actor_id,
            category_id=category_id,
            template_id=template_id,
            numbering_template_id=numbering_template_id,
            letterhead_id=letterhead_id,
            registration_date=registration_date or date.today(),
            letter_date=letter_date or date.today(),
            due_date=due_date,
            external_reference=external_reference,
            summary=summary,
            tag_ids=tag_ids,
            actor_id=actor_id,
            metadata=metadata,
        )

    # ── Update ────────────────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def update(
        letter: Letter,
        *,
        subject: str | None = None,
        body: str | None = None,
        priority: str | None = None,
        security: str | None = None,
        category_id: int | None = None,
        template_id: int | None = None,
        letterhead_id: int | None = None,
        registration_date: date | None = None,
        letter_date: date | None = None,
        received_date: date | None = None,
        due_date: date | None = None,
        sender_external: str | None = None,
        sender_organization: str | None = None,
        external_reference: str | None = None,
        summary: str | None = None,
        actor_id: int | None = None,
        metadata: dict | None = None,
    ) -> Letter:
        from simorgh.apps.events.bus import dispatch

        changed: list[str] = []
        for field, value in (
            ("subject", subject),
            ("body", body),
            ("priority", priority),
            ("security", security),
            ("category_id", category_id),
            ("template_id", template_id),
            ("letterhead_id", letterhead_id),
            ("registration_date", registration_date),
            ("letter_date", letter_date),
            ("received_date", received_date),
            ("due_date", due_date),
            ("sender_external", sender_external),
            ("sender_organization", sender_organization),
            ("external_reference", external_reference),
            ("summary", summary),
        ):
            if value is not None and getattr(letter, field) != value:
                setattr(letter, field, value)
                changed.append(field)

        if metadata is not None:
            letter.metadata = {**letter.metadata, **metadata}
            changed.append("metadata")

        if changed:
            letter.save(update_fields=[*changed, "updated_at"])

        dispatch(
            "correspondence.letter.updated",
            {
                "letter_id": letter.pk,
                "tenant_id": letter.tenant_id,
                "changed_fields": changed,
                "actor_id": actor_id,
            },
        )

        return letter

    # ── Status transitions ────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def change_status(
        letter: Letter,
        new_status: str,
        *,
        actor_id: int | None = None,
    ) -> Letter:
        from simorgh.apps.events.bus import dispatch

        old_status = letter.status
        letter.status = new_status

        if new_status == "sent":
            letter.sent_date = date.today()
        elif new_status == "archived" or new_status == "cancelled":
            pass

        letter.save(update_fields=["status", "updated_at"])

        dispatch(
            "correspondence.letter.status_changed",
            {
                "letter_id": letter.pk,
                "tenant_id": letter.tenant_id,
                "from_status": old_status,
                "to_status": new_status,
                "actor_id": actor_id,
            },
        )

        if new_status == "sent":
            dispatch(
                "correspondence.letter.sent",
                {
                    "letter_id": letter.pk,
                    "tenant_id": letter.tenant_id,
                    "sent_date": str(letter.sent_date),
                    "actor_id": actor_id,
                },
            )

        return letter

    @staticmethod
    @transaction.atomic
    def send(letter: Letter, *, actor_id: int | None = None) -> Letter:
        return LetterService.change_status(letter, "sent", actor_id=actor_id)

    @staticmethod
    @transaction.atomic
    def archive(letter: Letter, *, actor_id: int | None = None) -> Letter:
        from simorgh.apps.events.bus import dispatch

        result = LetterService.change_status(letter, "archived", actor_id=actor_id)
        dispatch(
            "correspondence.letter.archived",
            {
                "letter_id": letter.pk,
                "tenant_id": letter.tenant_id,
                "actor_id": actor_id,
            },
        )
        return result

    @staticmethod
    @transaction.atomic
    def cancel(
        letter: Letter, *, reason: str = "", actor_id: int | None = None
    ) -> Letter:
        from simorgh.apps.events.bus import dispatch

        result = LetterService.change_status(letter, "cancelled", actor_id=actor_id)
        dispatch(
            "correspondence.letter.cancelled",
            {
                "letter_id": letter.pk,
                "tenant_id": letter.tenant_id,
                "reason": reason,
                "actor_id": actor_id,
            },
        )
        return result

    # ── Recipients ────────────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def add_recipient(
        *,
        letter: Letter,
        recipient_id: int | None = None,
        recipient_name: str = "",
        recipient_organization: str = "",
        recipient_type: str = "to",
    ) -> LetterRecipient:
        from simorgh.apps.correspondence.models import LetterRecipient

        return LetterRecipient.objects.create(
            letter=letter,
            recipient_id=recipient_id,
            recipient_name=recipient_name,
            recipient_organization=recipient_organization,
            recipient_type=recipient_type,
            status="pending",
        )

    @staticmethod
    @transaction.atomic
    def add_recipients(
        *,
        letter: Letter,
        recipients: list[dict[str, Any]],
    ) -> list[LetterRecipient]:
        from simorgh.apps.correspondence.models import LetterRecipient

        objs = [
            LetterRecipient(
                letter=letter,
                recipient_id=r.get("recipient_id"),
                recipient_name=r.get("recipient_name", ""),
                recipient_organization=r.get("recipient_organization", ""),
                recipient_type=r.get("recipient_type", "to"),
                status="pending",
            )
            for r in recipients
        ]
        return list(LetterRecipient.objects.bulk_create(objs))

    # ── Reply ─────────────────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def reply(
        *,
        parent_letter: Letter,
        tenant_id: int,
        organization_node_id: int,
        subject: str | None = None,
        body: str = "",
        sender_id: int | None = None,
        priority: str = "normal",
        letter_type: str = "outgoing",
        actor_id: int | None = None,
    ) -> Letter:
        from simorgh.apps.correspondence.models import LetterReference
        from simorgh.apps.events.bus import dispatch

        reply_letter = LetterService.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            letter_type=letter_type,
            subject=subject or f"Re: {parent_letter.subject}",
            body=body,
            priority=priority,
            sender_id=sender_id or actor_id,
            parent_id=parent_letter.pk,
            actor_id=actor_id,
        )

        LetterReference.objects.create(
            from_letter=reply_letter,
            to_letter=parent_letter,
            reference_type="reply",
        )

        dispatch(
            "correspondence.reply.added",
            {
                "letter_id": reply_letter.pk,
                "parent_id": parent_letter.pk,
                "tenant_id": tenant_id,
                "actor_id": actor_id,
            },
        )

        return reply_letter

    # ── Attachment ────────────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def add_attachment(
        *,
        letter_id: int,
        file_id: int,
        attachment_type: str = "attachment",
        description: str = "",
        sort_order: int = 0,
    ) -> LetterAttachment:
        from simorgh.apps.correspondence.models import LetterAttachment

        return LetterAttachment.objects.create(
            letter_id=letter_id,
            file_id=file_id,
            attachment_type=attachment_type,
            description=description,
            sort_order=sort_order,
        )


# ═══════════════════════════════════════════════════════════════════════════
# CategoryService
# ═══════════════════════════════════════════════════════════════════════════


class CategoryService:
    """Service for Category CRUD."""

    @staticmethod
    @transaction.atomic
    def create(
        *,
        tenant_id: int,
        organization_node_id: int,
        name: str,
        category_type: str = "subject",
        parent_id: int | None = None,
        description: str = "",
        sort_order: int = 0,
    ) -> Category:
        from simorgh.apps.correspondence.models import Category

        return Category.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name,
            category_type=category_type,
            parent_id=parent_id,
            description=description,
            sort_order=sort_order,
            is_active=True,
        )

    @staticmethod
    @transaction.atomic
    def update(
        category: Category,
        *,
        name: str | None = None,
        category_type: str | None = None,
        parent_id: int | None = None,
        description: str | None = None,
        is_active: bool | None = None,
        sort_order: int | None = None,
    ) -> Category:
        for field, value in (
            ("name", name),
            ("category_type", category_type),
            ("parent_id", parent_id),
            ("description", description),
            ("is_active", is_active),
            ("sort_order", sort_order),
        ):
            if value is not None:
                setattr(category, field, value)
        category.save()
        return category


# ═══════════════════════════════════════════════════════════════════════════
# TemplateService
# ═══════════════════════════════════════════════════════════════════════════


class TemplateService:
    """Service for LetterTemplate, NumberingTemplate, LetterheadTemplate CRUD."""

    @staticmethod
    @transaction.atomic
    def create_letter_template(
        *,
        tenant_id: int,
        organization_node_id: int,
        name: str,
        letter_type: str = "outgoing",
        subject_template: str = "",
        body_template: str = "",
        letterhead_id: int | None = None,
        numbering_template_id: int | None = None,
        is_default: bool = False,
    ) -> LetterTemplate:
        from simorgh.apps.correspondence.models import LetterTemplate

        if is_default:
            LetterTemplate.objects.filter(
                tenant_id=tenant_id, letter_type=letter_type, is_default=True
            ).update(is_default=False)

        return LetterTemplate.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name,
            letter_type=letter_type,
            subject_template=subject_template,
            body_template=body_template,
            letterhead_id=letterhead_id,
            numbering_template_id=numbering_template_id,
            is_default=is_default,
            is_active=True,
        )

    @staticmethod
    @transaction.atomic
    def create_numbering_template(
        *,
        tenant_id: int,
        organization_node_id: int,
        name: str,
        pattern: str = "{year}/{counter:04d}",
        prefix: str = "",
        letter_type: str = "outgoing",
        is_default: bool = False,
    ) -> NumberingTemplate:
        from simorgh.apps.correspondence.models import NumberingTemplate

        if is_default:
            NumberingTemplate.objects.filter(
                tenant_id=tenant_id, letter_type=letter_type, is_default=True
            ).update(is_default=False)

        return NumberingTemplate.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name,
            pattern=pattern,
            prefix=prefix,
            letter_type=letter_type,
            is_default=is_default,
        )

    @staticmethod
    @transaction.atomic
    def create_letterhead_template(
        *,
        tenant_id: int,
        organization_node_id: int,
        name: str,
        header_html: str = "",
        footer_html: str = "",
        css_styles: str = "",
        is_default: bool = False,
    ) -> LetterheadTemplate:
        from simorgh.apps.correspondence.models import LetterheadTemplate

        if is_default:
            LetterheadTemplate.objects.filter(
                tenant_id=tenant_id, is_default=True
            ).update(is_default=False)

        return LetterheadTemplate.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            name=name,
            header_html=header_html,
            footer_html=footer_html,
            css_styles=css_styles,
            is_default=is_default,
            is_active=True,
        )
