import io
import json
import os
from typing import Any, Dict, Optional, Union
from urllib.parse import urljoin

import pandas as pd
import requests
from mnemosynecore.vault.client import get_secret_test

SharePointConn = Union[str, Dict[str, Any]]


def _parse_extra(extra_raw: Any) -> Dict[str, Any]:
    if isinstance(extra_raw, dict):
        return extra_raw
    if isinstance(extra_raw, str):
        try:
            parsed = json.loads(extra_raw) if extra_raw else {}
            if isinstance(parsed, dict):
                return parsed
        except json.JSONDecodeError:
            return {}
    return {}


def _resolve_sharepoint_config(
    sharepoint_conn: SharePointConn,
    dir_path: Optional[str] = None,
) -> Dict[str, Any]:
    if isinstance(sharepoint_conn, dict):
        cfg = dict(sharepoint_conn)
    elif isinstance(sharepoint_conn, str):
        raw = sharepoint_conn.strip()
        if raw.startswith("{"):
            try:
                cfg = json.loads(raw)
            except json.JSONDecodeError as exc:
                raise ValueError("Неверный формат JSON для конфигурации SharePoint") from exc
        else:
            from mnemosynecore.secrets import resolve_secret

            cfg = resolve_secret(raw, dir_path)
    else:
        raise TypeError("sharepoint_conn должен быть dict или str")

    extra = _parse_extra(cfg.get("extra"))
    out = dict(cfg)
    out["extra"] = extra
    out["host"] = out.get("host") or out.get("url") or out.get("sharepoint_url") or extra.get("host")
    out["login"] = out.get("login") or out.get("username") or out.get("user") or extra.get("login")
    out["password"] = out.get("password") or extra.get("password")
    out["domain"] = out.get("domain") or out.get("schema") or extra.get("domain")
    return out


def _resolve_sharepoint_config_test(
    sharepoint_conn: SharePointConn,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> Dict[str, Any]:
    if isinstance(sharepoint_conn, dict):
        return _resolve_sharepoint_config(sharepoint_conn, dir_path=None)
    if isinstance(sharepoint_conn, str):
        raw = sharepoint_conn.strip()
        if raw.startswith("{"):
            return _resolve_sharepoint_config(raw, dir_path=None)
        return _resolve_sharepoint_config(
            get_secret_test(raw, dir_path=dir_path, creds_path=creds_path),
            dir_path=None,
        )
    raise TypeError("sharepoint_conn должен быть dict или str")


def _build_request_url(host: str, file_url: str) -> str:
    file_url = file_url.strip()
    if file_url.startswith("http://") or file_url.startswith("https://"):
        return file_url
    return urljoin(host.rstrip("/") + "/", file_url.lstrip("/"))


def _build_ntlm_username(login: str, domain: Optional[str]) -> str:
    if not domain:
        return login
    if "\\" in login or "@" in login:
        return login
    return f"{domain}\\{login}"


def sharepoint_download_file(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> bytes:
    cfg = _resolve_sharepoint_config(sharepoint_conn, dir_path)

    host = cfg.get("host")
    login = cfg.get("login")
    password = cfg.get("password")
    domain = cfg.get("domain")

    if not host:
        raise ValueError("Для SharePoint нужен host/sharepoint_url")
    if auth_type != "none":
        if not login:
            raise ValueError("Для SharePoint нужен login/username")
        if password is None:
            raise ValueError("Для SharePoint нужен password")

    url = _build_request_url(host, file_url)
    request_headers = dict(headers or {})

    auth = None
    if auth_type.lower() == "ntlm":
        try:
            from requests_ntlm import HttpNtlmAuth
        except ImportError as exc:
            raise ImportError(
                "Для NTLM-доступа к SharePoint установите requests-ntlm "
                "(или mnemosynecore[sharepoint])"
            ) from exc
        auth = HttpNtlmAuth(_build_ntlm_username(login, domain), password)
    elif auth_type.lower() == "basic":
        auth = (login, password)
    elif auth_type.lower() == "none":
        auth = None
    else:
        raise ValueError("auth_type должен быть одним из: ntlm, basic, none")

    response = requests.get(
        url,
        auth=auth,
        headers=request_headers,
        timeout=timeout,
        verify=verify,
        allow_redirects=allow_redirects,
    )
    response.raise_for_status()
    return response.content


def sharepoint_download_to_file(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    output_path: str,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> str:
    content = sharepoint_download_file(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )

    out_dir = os.path.dirname(output_path)
    if out_dir:
        os.makedirs(out_dir, exist_ok=True)

    with open(output_path, "wb") as f:
        f.write(content)
    return output_path


def sharepoint_read_text(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    errors: str = "strict",
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> str:
    content = sharepoint_download_file(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )
    return content.decode(encoding, errors=errors)


def sharepoint_read_dataframe(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    file_format: str = "auto",
    encoding: str = "utf-8",
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    content = sharepoint_download_file(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )

    options = dict(read_kwargs or {})
    fmt = file_format.lower().strip()
    if fmt == "auto":
        path_lower = file_url.lower()
        if path_lower.endswith(".csv"):
            fmt = "csv"
        elif path_lower.endswith(".tsv"):
            fmt = "tsv"
        elif path_lower.endswith(".xlsx") or path_lower.endswith(".xls"):
            fmt = "excel"
        elif path_lower.endswith(".json"):
            fmt = "json"
        else:
            raise ValueError(
                "Не удалось определить формат файла автоматически. "
                "Передайте file_format явно (csv/tsv/excel/json)."
            )

    if fmt == "csv":
        return pd.read_csv(io.StringIO(content.decode(encoding)), **options)
    if fmt == "tsv":
        if "sep" not in options:
            options["sep"] = "\t"
        return pd.read_csv(io.StringIO(content.decode(encoding)), **options)
    if fmt == "excel":
        return pd.read_excel(io.BytesIO(content), **options)
    if fmt == "json":
        return pd.read_json(io.StringIO(content.decode(encoding)), **options)

    raise ValueError("file_format должен быть одним из: auto, csv, tsv, excel, json")


def sharepoint_read_csv(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_dataframe(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        file_format="csv",
        encoding=encoding,
        read_kwargs=read_kwargs,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_excel(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_dataframe(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        file_format="excel",
        read_kwargs=read_kwargs,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_json(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_dataframe(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        file_format="json",
        encoding=encoding,
        read_kwargs=read_kwargs,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_sql(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> str:
    return sharepoint_read_text(
        sharepoint_conn=sharepoint_conn,
        file_url=file_url,
        encoding=encoding,
        dir_path=dir_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_download_many(
    *,
    sharepoint_conn: SharePointConn,
    files: list,
    output_dir: str,
    dir_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
    continue_on_error: bool = False,
) -> Dict[str, Any]:
    os.makedirs(output_dir, exist_ok=True)
    downloaded = []
    failed = []

    for i, item in enumerate(files, start=1):
        file_url = item.get("file_url") or item.get("url")
        if not file_url:
            err = "file_url/url обязателен"
            if continue_on_error:
                failed.append({"item": item, "error": err})
                continue
            raise ValueError(err)

        file_name = item.get("file_name")
        if not file_name:
            file_name = os.path.basename(file_url.strip("/")) or f"file_{i}"
        output_path = os.path.join(output_dir, file_name)

        try:
            saved_path = sharepoint_download_to_file(
                sharepoint_conn=sharepoint_conn,
                file_url=file_url,
                output_path=output_path,
                dir_path=dir_path,
                timeout=timeout,
                verify=verify,
                headers=headers,
                auth_type=auth_type,
                allow_redirects=allow_redirects,
            )
            downloaded.append(saved_path)
        except Exception as exc:
            if continue_on_error:
                failed.append({"item": item, "error": str(exc)})
                continue
            raise

    return {"downloaded": downloaded, "failed": failed}


def sharepoint_download_file_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> bytes:
    return sharepoint_download_file(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_download_to_file_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    output_path: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> str:
    return sharepoint_download_to_file(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        output_path=output_path,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_text_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    errors: str = "strict",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> str:
    return sharepoint_read_text(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        encoding=encoding,
        errors=errors,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_dataframe_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    file_format: str = "auto",
    encoding: str = "utf-8",
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_dataframe(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        file_format=file_format,
        encoding=encoding,
        read_kwargs=read_kwargs,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_csv_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_csv(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        encoding=encoding,
        read_kwargs=read_kwargs,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_excel_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_excel(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        read_kwargs=read_kwargs,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_json_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    read_kwargs: Optional[Dict[str, Any]] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> pd.DataFrame:
    return sharepoint_read_json(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        encoding=encoding,
        read_kwargs=read_kwargs,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_read_sql_test(
    *,
    sharepoint_conn: SharePointConn,
    file_url: str,
    encoding: str = "utf-8",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
) -> str:
    return sharepoint_read_sql(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_url=file_url,
        encoding=encoding,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
    )


def sharepoint_download_many_test(
    *,
    sharepoint_conn: SharePointConn,
    files: list,
    output_dir: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    timeout: int = 60,
    verify: bool = True,
    headers: Optional[Dict[str, str]] = None,
    auth_type: str = "ntlm",
    allow_redirects: bool = True,
    continue_on_error: bool = False,
) -> Dict[str, Any]:
    return sharepoint_download_many(
        sharepoint_conn=_resolve_sharepoint_config_test(
            sharepoint_conn=sharepoint_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        files=files,
        output_dir=output_dir,
        timeout=timeout,
        verify=verify,
        headers=headers,
        auth_type=auth_type,
        allow_redirects=allow_redirects,
        continue_on_error=continue_on_error,
    )
