
import json
import logging
from urllib.parse import urljoin, urlparse

import requests
from django.conf import settings

log = logging.getLogger(__name__)

REDIRECT_CODES = (301, 302, 303, 307, 308)
MAX_REDIRECTS = 3
REQUEST_TIMEOUT = 20


class NaukriyanPushError(Exception):
    """Raised when an interview cannot be pushed to Naukriyan."""


def build_payload(iv):
    """
    Build the Naukriyan payload using the saved Interview,
    Question and ProctorEvent model fields.
    """

    questions = iv.questions.all().order_by("order")
    events = iv.events.select_related("question").all().order_by(
        "created_at"
    )

    return {
        # Interview model fields
        "jsuser_id": iv.jsuser_id,
        "job_id": iv.job_id,
        "interview_id": iv.pk,
        "candidate_name": iv.candidate_name,
        "jd": iv.jd,
        "status": iv.status or "pending",
        "hr_note": iv.hr_note or "",
        "decided_at": (
            iv.decided_at.isoformat() if iv.decided_at else None
        ),
        "overall_score": iv.overall_score,
        "ai_recommendation": iv.ai_recommendation,
        "report": iv.report or "",
        "integrity_score": iv.integrity_score,
        "total_distracted_seconds": iv.total_distracted_seconds,
        "terminated": iv.terminated,
        "terminated_reason": iv.terminated_reason,
        "terminated_at": (
            iv.terminated_at.isoformat()
            if iv.terminated_at else None
        ),
        "created_at": (
            iv.created_at.isoformat() if iv.created_at else None
        ),
        "video": iv.video.name if iv.video else None,

        # Question model fields
        "questions": [
            {
                "order": q.order,
                "question": q.text,
                "answer": q.answer,
                "score": q.score,
                "feedback": q.feedback,
                "typed_chars": q.typed_chars,
                "voice_chars": q.voice_chars,
                "paste_attempts": q.paste_attempts,
                "tab_switches": q.tab_switches,
                "suspicious_input": q.suspicious_input,
                "asked_at": (
                    q.asked_at.isoformat() if q.asked_at else None
                ),
                "answered_at": (
                    q.answered_at.isoformat()
                    if q.answered_at else None
                ),
                "time_taken_seconds": q.time_taken_seconds,
                "distracted_seconds": q.distracted_seconds,
                "distraction_count": q.distraction_count,
            }
            for q in questions
        ],

        # ProctorEvent model fields
        "proctor_events": [
            {
                "message": event.message,
                "created_at": (
                    event.created_at.isoformat()
                    if event.created_at else None
                ),
                "question_id": event.question_id,
                "question_order": (
                    event.question.order
                    if event.question else None
                ),
            }
            for event in events
        ],
    }


def _same_origin(first_url, second_url):
    """
    Return True only when both URLs have the same scheme,
    hostname and effective port.
    """

    first = urlparse(first_url)
    second = urlparse(second_url)

    def effective_port(parsed):
        if parsed.port is not None:
            return parsed.port

        if parsed.scheme.lower() == "https":
            return 443

        if parsed.scheme.lower() == "http":
            return 80

        return None

    return (
        first.scheme.lower() == second.scheme.lower()
        and (first.hostname or "").lower()
        == (second.hostname or "").lower()
        and effective_port(first) == effective_port(second)
    )


def _post_keep_method(url, body, headers):
    """
    POST JSON and manually handle redirects.

    The POST method is preserved. Redirects are followed only
    when the destination has the same origin as the original URL.
    Credentials are never forwarded to another origin.
    """

    original_url = url

    for attempt in range(MAX_REDIRECTS + 1):
        try:
            resp = requests.post(
                url,
                data=body,
                headers=headers,
                timeout=REQUEST_TIMEOUT,
                allow_redirects=False,
            )
        except requests.exceptions.RequestException as exc:
            raise NaukriyanPushError(
                "Network error while contacting Naukriyan."
            ) from exc

        if resp.status_code not in REDIRECT_CODES:
            return resp

        location = resp.headers.get("Location")

        if not location:
            return resp

        new_url = urljoin(url, location)
        new = urlparse(new_url)

        # Reject invalid redirect destinations.
        if (
            new.scheme.lower() not in ("http", "https")
            or not new.hostname
            or new.username
            or new.password
        ):
            raise NaukriyanPushError(
                "Naukriyan returned an invalid redirect URL."
            )

        # Do not forward credentials to a different origin.
        if not _same_origin(original_url, new_url):
            raise NaukriyanPushError(
                "Naukriyan redirected to a different origin. "
                "Check NAUKRIYAN_INTERVIEW_URL configuration."
            )

        if attempt == MAX_REDIRECTS:
            raise NaukriyanPushError(
                "Too many redirects while pushing interview."
            )

        log.warning(
            "Naukriyan redirect received: HTTP %s",
            resp.status_code,
        )

        url = new_url

    raise NaukriyanPushError(
        "Too many redirects while pushing interview."
    )


def push_interview(iv):
    """
    Push a saved interview to Naukriyan.

    The report must already be saved to the database.
    This function does not update synced_at.
    The caller updates synced_at after successful validation.
    """

    # 1. Refresh the latest saved database values.
    iv.refresh_from_db()

    # 2. Validate the required external IDs.
    if iv.jsuser_id is None or iv.job_id is None:
        raise NaukriyanPushError(
            f"Interview {iv.pk} is missing required IDs: "
            f"jsuser_id={iv.jsuser_id}, job_id={iv.job_id}"
        )

    # 3. Require a saved report.
    if not iv.report or not iv.report.strip():
        raise NaukriyanPushError(
            f"Interview {iv.pk} has no saved report. "
            "Finalize the interview before syncing."
        )

    # 4. Validate API configuration.
    url = getattr(settings, "NAUKRIYAN_INTERVIEW_URL", "")
    token = getattr(settings, "NAUKRIYAN_API_TOKEN", "")

    if not isinstance(url, str) or not url.strip():
        raise NaukriyanPushError(
            "NAUKRIYAN_INTERVIEW_URL is missing in Django settings."
        )

    if not isinstance(token, str) or not token.strip():
        raise NaukriyanPushError(
            "NAUKRIYAN_API_TOKEN is missing in Django settings."
        )

    url = url.strip()
    token = token.strip()

    parsed_url = urlparse(url)

    try:
        valid_port = parsed_url.port
    except ValueError as exc:
        raise NaukriyanPushError(
            "NAUKRIYAN_INTERVIEW_URL contains an invalid port."
        ) from exc

    if (
        parsed_url.scheme.lower() not in ("http", "https")
        or not parsed_url.hostname
        or parsed_url.username
        or parsed_url.password
        or parsed_url.fragment
    ):
        raise NaukriyanPushError(
            "NAUKRIYAN_INTERVIEW_URL must be a valid HTTP/HTTPS URL "
            "without embedded credentials or a fragment."
        )

    headers = {
        "X-Authorization": token,
        "Content-Type": "application/json",
        "Accept": "application/json",
    }

    # 5. Build the payload from the saved model data.
    try:
        payload = build_payload(iv)

        if not isinstance(payload, dict):
            raise ValueError(
                "build_payload(iv) must return a dictionary."
            )

        body = json.dumps(payload, default=str)
        
        # Print the outgoing payload in the terminal.
        print("\n" + "=" * 70)
        print("NAUKRIYAN API - OUTGOING INTERVIEW DATA")
        print("=" * 70)

        print(f"Interview ID : {iv.pk}")
        print(f"Candidate    : {iv.candidate_name}")
        print(f"JS User ID   : {iv.jsuser_id}")
        print(f"Job ID       : {iv.job_id}")
        print(f"API URL      : {url}")
        print(f"Report saved : {bool(iv.report)}")

        print("\nFULL JSON PAYLOAD BEING SENT:")
        print(json.dumps(payload, indent=4, ensure_ascii=False, default=str))

        print("\n" + "=" * 70)
        print("SENDING REQUEST TO NAUKRIYAN...")
        print("=" * 70 + "\n")
                


        log.info(
            "Preparing Naukriyan payload: interview_id=%s, "
            "question_count=%s, event_count=%s, report_saved=%s",
            iv.pk,
            len(payload["questions"]),
            len(payload["proctor_events"]),
            bool(iv.report),
        )

    except Exception as exc:
        log.exception(
            "Failed to build Naukriyan payload for interview %s",
            iv.pk,
        )
        raise NaukriyanPushError(
            f"Failed to build interview payload: {exc}"
        ) from exc

    # 6. Send the request.
    try:
        resp = _post_keep_method(url, body, headers)
        print("Naukriyan status:", resp.status_code)
        print("Naukriyan final URL:", resp.url)
        print("Naukriyan response:", resp.text[:2000])

    except NaukriyanPushError:
        log.exception(
            "Naukriyan request failed: interview_id=%s",
            iv.pk,
        )
        raise

    except Exception as exc:
        log.exception(
            "Unexpected Naukriyan request error: interview_id=%s",
            iv.pk,
        )
        raise NaukriyanPushError(
            "Unexpected error while sending interview to Naukriyan."
        ) from exc

    # 7. Validate the HTTP response.
    if not resp.ok:
        log.error(
            "Naukriyan HTTP failure: interview_id=%s, status=%s",
            iv.pk,
            resp.status_code,
        )
        

        raise NaukriyanPushError(
            f"Naukriyan API returned HTTP {resp.status_code}."
        )

    # 8. Validate the response body if it contains JSON.
    try:
        data = resp.json()
    except (ValueError, json.JSONDecodeError):
        data = None

        log.warning(
            "Naukriyan returned a non-JSON response: "
            "interview_id=%s, status=%s",
            iv.pk,
            resp.status_code,
        )

    if isinstance(data, dict):
        if data.get("success") is False:
            log.error(
                "Naukriyan rejected interview: interview_id=%s",
                iv.pk,
            )

            raise NaukriyanPushError(
                f"Naukriyan rejected interview {iv.pk}."
            )

        response_status = str(
            data.get("status", "")
        ).strip().lower()

        if response_status in {"error", "failed", "failure"}:
            raise NaukriyanPushError(
                f"Naukriyan reported failure for interview {iv.pk}."
            )

    # 9. Return only after the response passes validation.
    log.info(
        "Interview %s successfully pushed to Naukriyan. HTTP %s",
        iv.pk,
        resp.status_code,
    )

    return resp
