"""CSV parsing / serialisation helpers for lead import and export."""

from __future__ import annotations

import csv
import io
import re
from typing import Any

#: Accepted CSV headers -> internal field name. Header matching is case- and
#: separator-insensitive, so "Job Title", "job_title" and "jobTitle" all work.
HEADER_ALIASES: dict[str, str] = {
    "firstname": "first_name",
    "first": "first_name",
    "givenname": "first_name",
    "lastname": "last_name",
    "last": "last_name",
    "surname": "last_name",
    "familyname": "last_name",
    "fullname": "full_name",
    "name": "full_name",
    "jobtitle": "job_title",
    "title": "job_title",
    "position": "job_title",
    "role": "job_title",
    "company": "company",
    "companyname": "company",
    "organisation": "company",
    "organization": "company",
    "industry": "industry",
    "location": "location",
    "country": "location",
    "region": "location",
    "companysize": "company_size",
    "employees": "company_size",
    "headcount": "company_size",
    "size": "company_size",
    "headline": "headline",
    "about": "about",
    "summary": "about",
    "bio": "about",
    "profileurl": "profile_url",
    "linkedin": "profile_url",
    "linkedinurl": "profile_url",
    "profile": "profile_url",
    "url": "profile_url",
    "website": "website",
    "site": "website",
    "domain": "website",
    "keywords": "keywords",
    "tags": "tags",
}

IMPORT_FIELDS = [
    "first_name",
    "last_name",
    "full_name",
    "job_title",
    "company",
    "industry",
    "location",
    "company_size",
    "headline",
    "about",
    "profile_url",
    "website",
    "keywords",
    "tags",
]

EXPORT_COLUMNS = [
    ("id", "id"),
    ("firstName", "first_name"),
    ("lastName", "last_name"),
    ("fullName", "full_name"),
    ("jobTitle", "job_title"),
    ("company", "company"),
    ("industry", "industry"),
    ("location", "location"),
    ("companySize", "company_size"),
    ("headline", "headline"),
    ("about", "about"),
    ("profileUrl", "profile_url"),
    ("website", "website"),
    ("score", "score"),
    ("status", "status"),
    ("connectionStatus", "connection_status"),
    ("messageStatus", "message_status"),
    ("replyStatus", "reply_status"),
    ("source", "source"),
]

_SEPARATORS_RE = re.compile(r"[\s_\-.]+")
_NUMBER_RE = re.compile(r"-?\d[\d,]*")

#: Internal key holding the 1-based line number a row came from. Never a lead field.
SOURCE_ROW_KEY = "__source_row__"


def normalize_header(header: str) -> str | None:
    """Map a raw CSV header to an internal field name, or None when unknown."""
    if not header:
        return None
    key = _SEPARATORS_RE.sub("", header.strip().lower())
    return HEADER_ALIASES.get(key)


def parse_company_size(value: Any) -> int | None:
    """Turn '10-100', '51+', '1,200 employees' into a single integer."""
    if value is None or value == "":
        return None
    if isinstance(value, (int, float)):
        return int(value)
    matches = _NUMBER_RE.findall(str(value))
    if not matches:
        return None
    numbers = [int(match.replace(",", "")) for match in matches]
    return numbers[0]


def parse_list(value: Any) -> list[str]:
    if not value:
        return []
    if isinstance(value, list):
        return [str(item).strip() for item in value if str(item).strip()]
    return [item.strip() for item in re.split(r"[;,|]", str(value)) if item.strip()]


def read_csv(content: str | bytes) -> tuple[list[dict[str, Any]], list[str], list[str]]:
    """Parse CSV text into normalised row dicts.

    Returns ``(rows, recognised_headers, unknown_headers)``.
    """
    if isinstance(content, bytes):
        for encoding in ("utf-8-sig", "utf-8", "latin-1"):
            try:
                content = content.decode(encoding)
                break
            except UnicodeDecodeError:
                continue
        else:  # pragma: no cover - latin-1 never fails
            content = content.decode("utf-8", errors="replace")

    text = content.lstrip("﻿")
    sample = text[:4096]
    try:
        dialect = csv.Sniffer().sniff(sample, delimiters=",;\t|")
    except csv.Error:
        dialect = csv.excel

    reader = csv.DictReader(io.StringIO(text), dialect=dialect)
    recognised: list[str] = []
    unknown: list[str] = []
    mapping: dict[str, str] = {}
    for header in reader.fieldnames or []:
        field = normalize_header(header)
        if field:
            mapping[header] = field
            recognised.append(field)
        else:
            unknown.append(header)

    rows: list[dict[str, Any]] = []
    for raw_row in reader:
        row: dict[str, Any] = {}
        for header, field in mapping.items():
            value = raw_row.get(header)
            if value is None:
                continue
            value = value.strip()
            if not value:
                continue
            if field == "company_size":
                row[field] = parse_company_size(value)
            elif field in ("keywords", "tags"):
                row[field] = parse_list(value)
            else:
                row[field] = value
        if any(value not in (None, "", [], {}) for value in row.values()):
            # Carry the real line number so import errors point at the right line
            # of the user's file even when blank rows were skipped.
            row[SOURCE_ROW_KEY] = reader.line_num
            rows.append(row)
    return rows, recognised, unknown


def write_csv(records: list[Any], columns: list[tuple[str, str]] | None = None) -> str:
    """Serialise ORM objects (or dicts) to CSV text."""
    columns = columns or EXPORT_COLUMNS
    buffer = io.StringIO()
    writer = csv.writer(buffer, lineterminator="\n")
    writer.writerow([header for header, _ in columns])
    for record in records:
        row = []
        for _, attribute in columns:
            value = (
                record.get(attribute) if isinstance(record, dict) else getattr(record, attribute, None)
            )
            if isinstance(value, (list, tuple)):
                value = "; ".join(str(item) for item in value)
            row.append("" if value is None else value)
        writer.writerow(row)
    return buffer.getvalue()
