"""Job posting orchestration.

Views call these functions; provider specifics stay behind the integration
layer. One channel failing never blocks the others.
"""

import logging

from django.db import transaction
from django.utils import timezone

from ..models import JobDescription, JobPosting
from .integrations.base import IntegrationError
from .integrations.factory import get_integration

logger = logging.getLogger(__name__)


def post_job(job: JobDescription, channels: list[str], user) -> list[dict]:
    """Post a JD to each requested channel.

    Per channel:
      - already POSTED         → skip, report "ALREADY_POSTED" (no repost)
      - new / PENDING / FAILED → (re)try: PENDING row first, then the provider;
                                 success → POSTED, failure → FAILED + error_message.
    Always continues with remaining channels after a failure.
    Returns [{channel, status, external_url, error_message}, ...].
    """
    results = []

    for channel in channels:
        channel = (channel or "").upper()

        # One row per (job, channel) — reuse the row on retries.
        with transaction.atomic():
            posting, _created = JobPosting.objects.get_or_create(
                job=job,
                channel=channel,
                defaults={"status": JobPosting.Status.PENDING, "posted_by": user},
            )

            if posting.status == JobPosting.Status.POSTED:
                results.append({
                    "channel": channel,
                    "status": "ALREADY_POSTED",
                    "external_url": posting.external_url,
                    "error_message": "",
                })
                continue

            posting.status = JobPosting.Status.PENDING
            posting.error_message = ""
            posting.posted_by = user
            posting.save(update_fields=["status", "error_message", "posted_by", "updated_at"])

        try:
            integration = get_integration(channel)
            result = integration.post_job(job)
        except IntegrationError as exc:
            logger.warning("Posting JD %s to %s failed: %s", job.id, channel, exc)
            _mark(posting, JobPosting.Status.FAILED, error_message=str(exc))
            results.append({
                "channel": channel,
                "status": JobPosting.Status.FAILED,
                "external_url": "",
                "error_message": str(exc),
            })
            continue
        except Exception as exc:  # provider bug — log loudly, keep going
            logger.exception("Unexpected error posting JD %s to %s", job.id, channel)
            _mark(posting, JobPosting.Status.FAILED, error_message=f"Internal error: {exc}")
            results.append({
                "channel": channel,
                "status": JobPosting.Status.FAILED,
                "external_url": "",
                "error_message": f"Internal error: {exc}",
            })
            continue

        _mark(
            posting,
            JobPosting.Status.POSTED,
            external_post_id=result.external_post_id,
            external_url=result.external_url,
            posted_at=timezone.now(),
        )
        results.append({
            "channel": channel,
            "status": JobPosting.Status.POSTED,
            "external_url": result.external_url,
            "error_message": "",
        })

    return results


def _mark(posting: JobPosting, status: str, **fields) -> None:
    with transaction.atomic():
        posting.status = status
        for name, value in fields.items():
            setattr(posting, name, value)
        posting.save()
