#!/usr/bin/env python3
"""
Scannt den Sent-Folder nach ausgehenden Bewerbungen und POSTed sie
in die puls.db. Quelle-of-truth fuer "du hast X Bewerbungen raus".

Output: JSON mit den importierten Bewerbungen.
"""
from __future__ import annotations

import json
import os
import re
import subprocess
import sys
import urllib.request
from datetime import datetime
from pathlib import Path

HOME = Path(os.environ.get("USERPROFILE") or r"C:\Users\server")
os.environ["PATH"] = (
    str(HOME / ".local" / "bin") + os.pathsep +
    os.environ.get("PATH", "")
)
HIMALAYA = HOME / ".local" / "bin" / "himalaya.exe"
BACKEND = "http://127.0.0.1:8000"

# Parser importieren
sys.path.insert(0, str(HOME / ".hermes" / "scripts"))
from parse_himalaya_envelope import parse_envelope_table, fetch_full_subject  # noqa: E402


def list_sent(page: int = 1, page_size: int = 50):
    """Listet Sent-Folder via himalaya, parst die Tabelle, holt volle Subjects."""
    result = subprocess.run(
        [str(HIMALAYA), "envelope", "list", "-m", "Sent",
         "--page", str(page), "--page-size", str(page_size),
         "--max-width", "200"],
        capture_output=True, text=True, timeout=30,
    )
    if result.returncode != 0:
        print(f"Sent page {page}: rc={result.returncode}", file=sys.stderr)
        print(result.stderr, file=sys.stderr)
        return []
    envs = parse_envelope_table(result.stdout)
    for e in envs:
        if len(e["subject"]) >= 85:
            full = fetch_full_subject(e["id"])
            if full:
                e["subject"] = full
    return envs


# Bewerbungs-Indikatoren im SUBJECT oder BODY
BEWERB_HINTS = (
    "bewerbung", "bewerbe mich", "application", "apply", "stelle",
    "position", "job", "kennziffer", "referenznummer", "job-id",
    "vacancy", "vacatur", "initativbewerbung", "initiativbewerbung",
    "interesse an", "interest in", "softwareentwickler", "developer",
    "engineer", "frontend", "backend", "fullstack", "devops",
)

# Ausschluss: Newsletter-Antworten, persönliche Mails
SKIP_HINTS = (
    "rechnung", "invoice", "payment", "zahlung",
    "abo", "subscription", "newsletter",
    "vertrag", "contract",
)


def looks_like_bewerbung(subject: str, body: str = "") -> bool:
    """True wenn Subject oder Body nach Bewerbung klingt."""
    hay = (subject + " " + body).lower()
    if any(s in hay for s in SKIP_HINTS):
        return False
    return any(b in hay for b in BEWERB_HINTS)


def fetch_body(env_id: str, max_chars: int = 4000) -> str:
    """Holt Mail-Body, quoted-printable decoded."""
    try:
        result = subprocess.run(
            [str(HIMALAYA), "message", "read", env_id],
            capture_output=True, text=True, timeout=20,
        )
        if result.returncode != 0:
            return ""
        body_start = result.stdout.find("\n\n")
        body = result.stdout[body_start + 2:] if body_start != -1 else result.stdout
        body = re.sub(r"=\r?\n", "", body)
        body = re.sub(r"=([0-9A-Fa-f]{2})", lambda m: chr(int(m.group(1), 16)), body)
        return body[:max_chars]
    except Exception:
        return ""


# Empfänger-Parsing — To/Cc aus der Mail-Header
TO_RE = re.compile(r"^To:\s*(.+)$", re.IGNORECASE | re.MULTILINE)
FROM_RE = re.compile(r"^From:\s*(.+)$", re.IGNORECASE | re.MULTILINE)


def parse_address(raw: str) -> tuple[str, str]:
    """Parse 'Max Mustermann <max@firma.de>' -> (name, email)."""
    raw = raw.strip()
    # "Name <email>"
    m = re.match(r"(.+?)\s*<([^>]+)>", raw)
    if m:
        return m.group(1).strip().strip('"'), m.group(2).strip()
    # bare email
    if "@" in raw:
        return "", raw.strip()
    return raw, ""


def extract_company_from_email(email: str) -> str | None:
    """Aus info@secova.de -> secova (ohne TLD, ohne www/info/etc.)."""
    if not email or "@" not in email:
        return None
    domain = email.split("@", 1)[1].lower()
    parts = domain.split(".")
    if len(parts) < 2:
        return None
    # Skip generische Subdomains
    generic = ("www", "mail", "smtp", "info", "contact", "office", "personal",
               "bewerbung", "bewerbungen", "jobs", "karriere", "career", "hr",
               "recruiting", "apply", "application")
    for p in parts[:-1]:
        if p not in generic:
            return p.capitalize()
    # Wenn alles generic war, nimm den zweiten-to-last
    return parts[-2].capitalize()


def backend_get(path):
    with urllib.request.urlopen(BACKEND + path, timeout=10) as r:
        return json.loads(r.read().decode())


def backend_post(path, payload):
    req = urllib.request.Request(
        BACKEND + path,
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            return r.status, json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        return e.code, {"error": e.read().decode("utf-8", errors="replace")[:200]}


def backend_put(path, payload):
    req = urllib.request.Request(
        BACKEND + path,
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
        method="PUT",
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            return r.status, json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        return e.code, {"error": e.read().decode("utf-8", errors="replace")[:200]}


# Position-Extraktion aus Subject + Body
POS_PATTERNS = [
    re.compile(r"(?:für|als|on|for|position|stelle)\s*[:\-]?\s*([A-ZÄÖÜ][^\s,;]+(?:\s+[A-ZÄÖÜ][^\s,;]+){0,5})", re.IGNORECASE),
    re.compile(r"(?:bewerbung|bewerbe mich)\s+(?:als|um|for|on)\s+([A-Za-zäöüÄÖÜß][^\s,;]+(?:\s+[A-Za-zäöüÄÖÜß][^\s,;]+){0,4})", re.IGNORECASE),
    re.compile(r"^([A-ZÄÖÜ][A-Za-zäöüÄÖÜß/.\-]+(?:\s+[A-ZÄÖÜ][A-Za-zäöüÄÖÜß/.\-]+){0,4})", re.MULTILINE),
]


def extract_position(subject: str, body: str) -> str:
    hay = subject + "\n" + body[:1500]
    for pat in POS_PATTERNS:
        m = pat.search(hay)
        if m:
            pos = m.group(1).strip()
            # Trim trailing common suffixes
            pos = re.sub(r"\s*\([^)]+\)\s*$", "", pos)
            pos = re.sub(r"\s+in\s+\w.*$", "", pos, flags=re.IGNORECASE)
            if 3 <= len(pos) <= 80:
                return pos
    return "unbekannt"


def main():
    # 3 Seiten Sent = 150 neueste ausgehende Mails
    all_sent = []
    for page in (1, 2, 3):
        envs = list_sent(page=page, page_size=50)
        if not envs:
            break
        all_sent.extend(envs)
        print(f"  page {page}: {len(envs)} envelopes")

    print(f"\nTotal Sent-Scan: {len(all_sent)} Mails")

    # Bewerbungs-Filter
    candidates = []
    for env in all_sent:
        body = fetch_body(env["id"], max_chars=2000)
        if looks_like_bewerbung(env["subject"], body):
            candidates.append((env, body))

    print(f"Bewerbungs-Kandidaten: {len(candidates)}")

    # Existing-DB-Lookup
    existing = backend_get("/applications")
    existing_by_company = {a["company"].lower(): a for a in existing}

    results = []
    for env, body in candidates:
        # Empfänger aus Body extrahieren (To: header)
        to_match = TO_RE.search(body[:500])
        sender_match = FROM_RE.search(body[:500])
        to_addr = parse_address(to_match.group(1))[1] if to_match else ""
        company = extract_company_from_email(to_addr) or "Unbekannt"

        position = extract_position(env["subject"], body)
        date_str = env["date"]
        applied_at = datetime.utcnow().date().isoformat()
        m_date = re.search(r"(\d{4}-\d{2}-\d{2})", date_str)
        if m_date:
            applied_at = m_date.group(1)

        # Match auf existierende
        existing_entry = existing_by_company.get(company.lower())
        if existing_entry:
            # Update (nur applied_at + notes)
            code, resp = backend_put(
                f"/applications/{existing_entry['id']}",
                {
                    "applied_at": applied_at,
                    "source": existing_entry.get("source") or "Email",
                    "notes": (existing_entry.get("notes", "") + f"\n--\nSent-Mail {applied_at}: {env['subject'][:80]}").strip(),
                }
            )
            results.append({
                "action": f"UPDATE existing #{existing_entry['id']} ({company})",
                "env_id": env["id"],
                "subject": env["subject"][:80],
                "rc": code,
            })
        else:
            # Neu anlegen
            code, resp = backend_post("/applications", {
                "company": company,
                "position": position,
                "status": "open",
                "source": "Email (Sent)",
                "applied_at": applied_at,
                "notes": f"Auto-Import Sent: {env['subject'][:120]}\nAn: {to_addr}",
                "contact_email": to_addr,
            })
            results.append({
                "action": f"CREATE ({company} - {position})",
                "env_id": env["id"],
                "subject": env["subject"][:80],
                "rc": code,
                "id": resp.get("id") if isinstance(resp, dict) else None,
            })

    print(json.dumps({"scanned": len(all_sent), "candidates": len(candidates), "results": results}, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
