"""Hand-rolled PDF document engine + the candidate report renderer.

No ReportLab/WeasyPrint in the offline venv, so this builds PDF object
streams directly: standard Type1 fonts (no embedding), vector rectangles
for branding/score bars/badges, flowed text with word wrap, automatic page
breaks, and a header/footer with page numbers stamped at finalize time.
"""

import io

# Brand palette (0-1 RGB)
INDIGO = (0.25, 0.32, 0.54)      # #405189
INDIGO_LIGHT = (0.91, 0.93, 0.98)
SLATE = (0.29, 0.33, 0.39)
MUTED = (0.55, 0.58, 0.64)
GREEN = (0.04, 0.70, 0.61)       # #0ab39c
AMBER = (0.97, 0.72, 0.29)       # #f7b84b
RED = (0.94, 0.40, 0.28)         # #f06548
LIGHT = (0.95, 0.96, 0.97)
WHITE = (1, 1, 1)

CLASSIFICATION_COLORS = {
    "Highly Recommended": GREEN,
    "Recommended": (0.16, 0.61, 0.86),
    "Consider": AMBER,
    "Not Recommended": RED,
}


# Common punctuation outside latin-1, mapped to safe equivalents so candidate
# data (smart quotes, em dashes, bullets) never renders as '?'.
_TRANSLIT = str.maketrans({
    "—": "-", "–": "-", "•": "\xb7", "…": "...",
    "‘": "'", "’": "'", "“": '"', "”": '"', "₹": "Rs ",
})


def _esc(text):
    return (
        str(text).translate(_TRANSLIT)
        .replace("\\", r"\\").replace("(", r"\(").replace(")", r"\)")
        .encode("latin-1", "replace").decode("latin-1")
    )


def _width(text, size, bold=False):
    """Approximate Helvetica string width in points."""
    return len(str(text)) * size * (0.53 if bold else 0.50)


def _wrap(text, size, max_width, bold=False):
    lines = []
    for raw_line in str(text).split("\n"):
        words, line = raw_line.split(), ""
        for word in words:
            candidate = f"{line} {word}".strip()
            if _width(candidate, size, bold) <= max_width or not line:
                line = candidate
            else:
                lines.append(line)
                line = word
        lines.append(line)
    return lines or [""]


class PdfDoc:
    PAGE_W, PAGE_H = 595, 842        # A4 portrait
    MARGIN = 48
    HEADER_H = 46
    FOOTER_H = 56

    def __init__(self, header_title=""):
        self.header_title = header_title
        self.pages = []               # list of op-lists
        self.cover_pages = 0          # pages without the standard header
        self._new_page_ops()

    # ── low-level ops ─────────────────────────────────────────────────────

    def _new_page_ops(self):
        self.ops = []
        self.pages.append(self.ops)
        self.y = self.PAGE_H - self.MARGIN - (self.HEADER_H if len(self.pages) > self.cover_pages else 0)

    def text(self, x, y, s, font="F1", size=10, color=(0, 0, 0)):
        r, g, b = color
        self.ops.append(
            f"BT {r:.3f} {g:.3f} {b:.3f} rg /{font} {size} Tf {x:.1f} {y:.1f} Td ({_esc(s)}) Tj ET"
        )

    def rect(self, x, y, w, h, color):
        r, g, b = color
        self.ops.append(f"{r:.3f} {g:.3f} {b:.3f} rg {x:.1f} {y:.1f} {w:.1f} {h:.1f} re f")

    def hline(self, x1, x2, y, color=LIGHT, width=0.8):
        r, g, b = color
        self.ops.append(f"{r:.3f} {g:.3f} {b:.3f} RG {width} w {x1:.1f} {y:.1f} m {x2:.1f} {y:.1f} l S")

    # ── flow layout ───────────────────────────────────────────────────────

    @property
    def usable_w(self):
        return self.PAGE_W - 2 * self.MARGIN

    def new_page(self):
        self._new_page_ops()

    def ensure(self, height):
        if self.y - height < self.MARGIN + self.FOOTER_H:
            self.new_page()

    def spacer(self, h=10):
        self.y -= h

    def heading(self, title, number=None):
        self.ensure(46)
        label = f"Section {number}  ·  {title}" if number else title
        self.rect(self.MARGIN, self.y - 20, 3.5, 16, INDIGO)
        self.text(self.MARGIN + 12, self.y - 17, label, font="F2", size=12.5, color=INDIGO)
        self.y -= 26
        self.hline(self.MARGIN, self.PAGE_W - self.MARGIN, self.y, LIGHT)
        self.y -= 14

    def paragraph(self, s, size=9.5, color=SLATE, font="F1", leading=None, indent=0):
        leading = leading or size + 4
        for line in _wrap(s, size, self.usable_w - indent, bold=(font == "F2")):
            self.ensure(leading + 4)
            self.text(self.MARGIN + indent, self.y - size, line, font=font, size=size, color=color)
            self.y -= leading

    def bullets(self, items, size=9.5, color=SLATE):
        for item in items:
            first = True
            for line in _wrap(item, size, self.usable_w - 16):
                self.ensure(size + 8)
                if first:
                    # \xb7 (·) is latin-1 safe; U+2022 (•) is not
                    self.text(self.MARGIN + 4, self.y - size, "\xb7", font="F2", size=size + 2, color=INDIGO)
                    first = False
                self.text(self.MARGIN + 16, self.y - size, line, size=size, color=color)
                self.y -= size + 4
        self.y -= 2

    def kv_table(self, pairs, label_w=150):
        row_h = 19
        for label, value in pairs:
            value_lines = _wrap(value if value not in (None, "") else "-", 9.5, self.usable_w - label_w - 20)
            block_h = row_h * len(value_lines)
            self.ensure(block_h + 2)
            if (len(self.ops) // 7) % 2 == 0:  # subtle zebra based on op parity is unstable; shade every row lightly
                pass
            self.rect(self.MARGIN, self.y - block_h, self.usable_w, block_h, (0.985, 0.985, 0.99))
            self.hline(self.MARGIN, self.PAGE_W - self.MARGIN, self.y - block_h, LIGHT, 0.5)
            self.text(self.MARGIN + 8, self.y - 13, label, font="F2", size=8.5, color=MUTED)
            for i, line in enumerate(value_lines):
                self.text(self.MARGIN + label_w, self.y - 13 - i * row_h, line, size=9.5, color=(0.1, 0.12, 0.16))
            self.y -= block_h

    def score_bar(self, label, value, max_value=100):
        self.ensure(30)
        bar_x = self.MARGIN + 150
        bar_w = self.usable_w - 150 - 46
        pct = max(0, min(1, value / max_value))
        color = GREEN if value >= 75 else AMBER if value >= 50 else RED
        self.text(self.MARGIN, self.y - 12, label, font="F2", size=9, color=SLATE)
        self.rect(bar_x, self.y - 14, bar_w, 8, LIGHT)
        if pct > 0:
            self.rect(bar_x, self.y - 14, bar_w * pct, 8, color)
        self.text(bar_x + bar_w + 8, self.y - 13, f"{value}%", font="F2", size=9, color=color)
        self.y -= 24

    def badge(self, s, color, x=None, size=10):
        w = _width(s, size, bold=True) + 20
        x = x if x is not None else self.MARGIN
        self.ensure(30)
        self.rect(x, self.y - 20, w, 20, color)
        self.text(x + 10, self.y - 14, s, font="F2", size=size, color=WHITE)
        self.y -= 28
        return w

    # ── output ────────────────────────────────────────────────────────────

    def _stamp_chrome(self, footer_meta):
        total = len(self.pages)
        for i, ops in enumerate(self.pages):
            page_no = i + 1
            chrome = []
            if page_no > self.cover_pages:
                # Header band
                r, g, b = INDIGO
                chrome.append(f"{r:.3f} {g:.3f} {b:.3f} rg 0 {self.PAGE_H - 30:.1f} {self.PAGE_W} 30 re f")
                chrome.append(
                    f"BT 1 1 1 rg /F2 10 Tf {self.MARGIN} {self.PAGE_H - 20:.1f} Td (TA-ATS  ·  Talent Acquisition) Tj ET"
                )
                title = _esc(self.header_title)
                tw = _width(self.header_title, 9, bold=False)
                chrome.append(
                    f"BT 0.85 0.88 0.96 rg /F1 9 Tf {self.PAGE_W - self.MARGIN - tw:.1f} {self.PAGE_H - 20:.1f} Td ({title}) Tj ET"
                )
            # Footer
            fy = self.MARGIN - 14
            lr, lg, lb = LIGHT
            chrome.append(f"{lr:.3f} {lg:.3f} {lb:.3f} RG 0.8 w {self.MARGIN} {fy + 26} m {self.PAGE_W - self.MARGIN} {fy + 26} l S")
            mr, mg, mb = MUTED
            chrome.append(
                f"BT {mr:.3f} {mg:.3f} {mb:.3f} rg /F1 7.5 Tf {self.MARGIN} {fy + 14} Td ({_esc(footer_meta)}) Tj ET"
            )
            pn = f"Page {page_no} of {total}"
            pw = _width(pn, 7.5)
            chrome.append(
                f"BT {mr:.3f} {mg:.3f} {mb:.3f} rg /F1 7.5 Tf {self.PAGE_W - self.MARGIN - pw:.1f} {fy + 14} Td ({_esc(pn)}) Tj ET"
            )
            notice = ("This document contains confidential candidate evaluation information "
                      "and is intended for internal recruitment purposes only.")
            nw = _width(notice, 6.5)
            chrome.append(
                f"BT {mr:.3f} {mg:.3f} {mb:.3f} rg /F3 6.5 Tf {(self.PAGE_W - nw) / 2:.1f} {fy + 3} Td ({_esc(notice)}) Tj ET"
            )
            ops.extend(chrome)

    def to_bytes(self, footer_meta="Generated by ATS System"):
        self._stamp_chrome(footer_meta)

        objects = {
            1: b"<< /Type /Catalog /Pages 2 0 R >>",
            3: b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
            4: b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>",
            5: b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Oblique >>",
        }
        page_ids = []
        next_id = 6
        for ops in self.pages:
            page_id, content_id = next_id, next_id + 1
            next_id += 2
            page_ids.append(page_id)
            stream = "\n".join(ops).encode("latin-1")
            objects[page_id] = (
                f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {self.PAGE_W} {self.PAGE_H}] "
                f"/Resources << /Font << /F1 3 0 R /F2 4 0 R /F3 5 0 R >> >> /Contents {content_id} 0 R >>"
            ).encode("latin-1")
            objects[content_id] = (
                f"<< /Length {len(stream)} >>\nstream\n".encode("latin-1") + stream + b"\nendstream"
            )
        kids = " ".join(f"{pid} 0 R" for pid in page_ids)
        objects[2] = f"<< /Type /Pages /Kids [{kids}] /Count {len(page_ids)} >>".encode("latin-1")

        out = io.BytesIO()
        out.write(b"%PDF-1.4\n")
        offsets = {}
        for obj_id in sorted(objects):
            offsets[obj_id] = out.tell()
            out.write(f"{obj_id} 0 obj\n".encode("latin-1"))
            out.write(objects[obj_id])
            out.write(b"\nendobj\n")
        xref_pos = out.tell()
        count = len(objects) + 1
        out.write(f"xref\n0 {count}\n".encode("latin-1"))
        out.write(b"0000000000 65535 f \n")
        for obj_id in sorted(objects):
            out.write(f"{offsets[obj_id]:010d} 00000 n \n".encode("latin-1"))
        out.write(f"trailer\n<< /Size {count} /Root 1 0 R >>\nstartxref\n{xref_pos}\n%%EOF".encode("latin-1"))
        return out.getvalue()


# ─── Candidate report renderer ───────────────────────────────────────────────


def render_candidate_report(payload, version=1):
    cand = payload["candidate"]
    job = payload["job"]
    meta = payload["meta"]

    doc = PdfDoc(header_title=f"Candidate Evaluation Report  ·  v{version}")
    doc.cover_pages = 1

    # ── Section 1: cover page ──
    doc.rect(0, doc.PAGE_H - 300, doc.PAGE_W, 300, INDIGO)
    doc.rect(doc.MARGIN, doc.PAGE_H - 118, 54, 54, WHITE)                     # vector logo block
    doc.text(doc.MARGIN + 8, doc.PAGE_H - 98, "ATS", font="F2", size=20, color=INDIGO)
    doc.text(doc.MARGIN + 70, doc.PAGE_H - 88, "TA-ATS", font="F2", size=22, color=WHITE)
    doc.text(doc.MARGIN + 70, doc.PAGE_H - 106, "Talent Acquisition & Tracking System", size=10, color=(0.8, 0.84, 0.95))
    doc.text(doc.MARGIN, doc.PAGE_H - 170, "CANDIDATE EVALUATION REPORT", font="F2", size=13, color=(0.75, 0.8, 0.95))
    doc.text(doc.MARGIN, doc.PAGE_H - 210, cand["name"] or "Unnamed Candidate", font="F2", size=28, color=WHITE)
    doc.text(doc.MARGIN, doc.PAGE_H - 232, f"Candidate ID: CAND-{cand['id']:05d}", size=11, color=(0.85, 0.88, 0.97))

    doc.y = doc.PAGE_H - 340
    doc.kv_table([
        ("Applied Position", job["title"] or "General talent pool"),
        ("Client", job["client"] or "Internal"),
        ("Current Stage", job["stage"] or "Not in pipeline"),
        ("Report Version", f"v{version}"),
        ("Generated", meta["generated_at"]),
    ], label_w=170)

    doc.y = doc.MARGIN + 120
    doc.hline(doc.MARGIN, doc.PAGE_W - doc.MARGIN, doc.y, LIGHT)
    doc.y -= 16
    doc.paragraph(
        "This report was generated automatically by the TA-ATS platform from the candidate's "
        "profile, pipeline activity and screening evaluations. Scores and recommendations are "
        "advisory and should be reviewed alongside interviewer judgement.",
        size=8.5, color=MUTED, font="F3",
    )

    # ── Section 2: candidate profile ──
    doc.new_page()
    doc.heading("Candidate Profile", 2)
    doc.kv_table([
        ("Full Name", cand["name"]),
        ("Email", cand["email"]),
        ("Mobile Number", cand["phone"]),
        ("Current Location", cand["location"]),
        ("Experience", f"{cand['experience_years']:g} years" if cand["experience_years"] else "Fresher"),
        ("Current Company", cand["current_company"]),
        ("Current Role", cand["current_role"]),
        ("Skills", ", ".join(cand["skills"]) if cand["skills"] else "—"),
        ("Resume File", cand["resume_file"]),
        ("Resume Upload Date", cand["resume_uploaded"]),
        ("Profile Status", cand["status"]),
    ])
    if cand["education"]:
        doc.spacer(14)
        doc.paragraph("Education", font="F2", size=10, color=INDIGO)
        for e in cand["education"]:
            line = f"{e['degree']}{' in ' + e['field'] if e['field'] else ''}"
            detail = " — ".join(x for x in [e["institution"], str(e["year"]) if e["year"] else ""] if x)
            doc.bullets([f"{line}{' (' + detail + ')' if detail else ''}"])

    # ── Section 3: resume summary ──
    doc.spacer(8)
    summary = payload["summary"]
    doc.heading("Resume Summary", 3)
    doc.paragraph(summary["professional_summary"])
    doc.spacer(6)
    doc.paragraph("Key Strengths", font="F2", size=10, color=INDIGO)
    doc.bullets(summary["key_strengths"])
    doc.paragraph("Relevant Technologies", font="F2", size=10, color=INDIGO)
    doc.paragraph(", ".join(summary["technologies"]) if summary["technologies"] else "None recorded")
    doc.spacer(6)
    doc.paragraph("Domain Experience", font="F2", size=10, color=INDIGO)
    doc.paragraph(summary["domain_experience"])
    if summary["career_highlights"]:
        doc.spacer(6)
        doc.paragraph("Career Highlights", font="F2", size=10, color=INDIGO)
        doc.bullets(summary["career_highlights"])

    # ── Section 4: interview transcript ──
    doc.spacer(8)
    doc.heading("Interview Transcript", 4)
    interview = payload["interview"]
    if interview:
        doc.kv_table([
            ("Interviewer", interview["interviewer"]),
            ("Interview Date", interview["date"]),
            ("Result", interview["result"]),
            ("Language", interview["language"]),
            ("Duration", interview["duration"] or "Not recorded"),
        ])
        if interview["notes"]:
            doc.spacer(10)
            doc.paragraph("Interviewer Notes", font="F2", size=10, color=INDIGO)
            doc.paragraph(interview["notes"])
        doc.spacer(8)
    if not interview or not interview.get("transcript"):
        doc.paragraph("No interview transcript available.", font="F3", color=MUTED)
    else:
        doc.paragraph(interview["transcript"])

    # ── Section 5: candidate scores ──
    doc.spacer(8)
    scores = payload["scores"]
    doc.heading("Candidate Scores", 5)
    doc.score_bar("Communication", scores["communication"])
    doc.score_bar("Technical", scores["technical"])
    doc.score_bar("Problem Solving", scores["problem_solving"])
    doc.score_bar("Experience Match", scores["experience_match"])
    doc.score_bar("Skill Match", scores["skill_match"])
    doc.spacer(4)
    doc.score_bar("OVERALL SCORE", scores["overall"])

    # ── Section 6: classification ──
    doc.spacer(8)
    cls = payload["classification"]
    doc.heading("Classification", 6)
    doc.badge(cls["value"].upper(), CLASSIFICATION_COLORS.get(cls["value"], INDIGO), size=11)
    doc.spacer(2)
    doc.kv_table([
        ("Confidence Score", f"{cls['confidence']}%"),
        ("Skill Matching", f"{cls['matching_percentage']}%"),
    ])

    # ── Section 7: AI recommendation ──
    doc.spacer(8)
    rec = payload["recommendation"]
    doc.heading("AI Recommendation", 7)
    doc.paragraph("Recommendation", font="F2", size=10, color=INDIGO)
    doc.paragraph(rec["summary"])
    doc.spacer(6)
    doc.paragraph("Strengths", font="F2", size=10, color=INDIGO)
    doc.bullets(rec["strengths"])
    doc.paragraph("Weaknesses", font="F2", size=10, color=INDIGO)
    doc.bullets(rec["weaknesses"])
    if rec["skill_gaps"]:
        doc.paragraph("Skill Gaps", font="F2", size=10, color=INDIGO)
        doc.bullets(rec["skill_gaps"])
    doc.paragraph("Suggested Interview Areas", font="F2", size=10, color=INDIGO)
    doc.bullets(rec["interview_areas"])
    doc.spacer(2)
    doc.kv_table([("Suggested Next Stage", rec["next_stage"])])
    doc.spacer(8)
    doc.paragraph(rec["notes"], font="F3", size=8.5, color=MUTED)

    # ── Section 8: footer is stamped on every page by to_bytes() ──
    footer = f"Generated by ATS System  ·  {meta['generated_at']}  ·  {meta['system_version']}"
    return doc.to_bytes(footer_meta=footer)
