"""Lightweight connection-test for FTP / FTPS / SFTP credentials.

Connects, authenticates, queries the working directory, and lists it.
Returns a structured result; never raises, never transfers data.

Reasons returned:
    ok          - success
    no_password - credential has no stored password (likely key-based auth)
    no_host     - extra_json missing 'host'
    timeout     - network/connection timeout
    auth        - credentials rejected
    network     - unreachable / refused / DNS
    tls         - TLS handshake or cert problem
    no_access   - logged in but directory listing failed
    unsupported - cred type not supported
    error       - unexpected exception (see detail)
"""

from __future__ import annotations

import logging
import socket
from typing import Dict, Optional

logger = logging.getLogger(__name__)

CONNECT_TIMEOUT = 10  # seconds
LOGIN_TIMEOUT = 10
LIST_TIMEOUT = 10
DEFAULT_PORTS = {"ftp": 21, "ftps": 21, "sftp": 22}


def _err(reason: str, detail: str = "") -> Dict:
    return {"ok": False, "reason": reason, "detail": detail}


def test_credential(cred_type: str, mode: Optional[str], host: Optional[str],
                    port: Optional[int], username: Optional[str],
                    password: Optional[str]) -> Dict:
    """Run a connect+login+list test. Returns {ok, reason, detail, home?}."""
    if cred_type != "ftp":
        return _err("unsupported", f"Credential type '{cred_type}' is not supported")
    effective_mode = (mode or "ftp").lower()
    if effective_mode not in DEFAULT_PORTS:
        return _err("unsupported", f"Unknown FTP mode '{mode}'")
    if not host:
        return _err("no_host", "No host stored on credential")
    if not password:
        if effective_mode == "sftp":
            return _err("no_password", "No password stored — likely key-based auth")
        return _err("no_password", "No password stored on credential")

    port = port or DEFAULT_PORTS[effective_mode]

    if effective_mode == "sftp":
        return _test_sftp(host, port, username or "", password)
    return _test_ftp(host, port, username or "", password, use_tls=(effective_mode == "ftps"))


def _test_ftp(host: str, port: int, username: str, password: str, use_tls: bool) -> Dict:
    from ftplib import FTP, FTP_TLS, error_perm, error_temp, error_proto
    import ssl

    ftp = None
    try:
        if use_tls:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE  # many FTPS servers use self-signed certs
            ftp = FTP_TLS(context=ctx, timeout=CONNECT_TIMEOUT)
        else:
            ftp = FTP(timeout=CONNECT_TIMEOUT)
        try:
            ftp.connect(host, port, timeout=CONNECT_TIMEOUT)
        except socket.timeout:
            return _err("timeout", f"Could not connect to {host}:{port} within {CONNECT_TIMEOUT}s")
        except (socket.gaierror, OSError) as exc:
            return _err("network", str(exc))

        try:
            ftp.login(user=username or "anonymous", passwd=password)
        except error_perm as exc:
            msg = str(exc)
            if any(k in msg for k in ("530", "Login", "incorrect", "denied")):
                return _err("auth", msg)
            return _err("auth", msg)
        except (error_temp, error_proto) as exc:
            return _err("error", str(exc))

        if use_tls:
            try:
                ftp.prot_p()  # turn on data-channel protection
            except Exception as exc:
                logger.info("FTPS prot_p failed (continuing): %s", exc)

        try:
            home = ftp.pwd()
        except Exception:
            home = "?"

        ftp.set_pasv(True)
        try:
            try:
                items = []
                ftp.retrlines("MLSD", lambda line: items.append(line))
            except (error_perm, error_temp, error_proto):
                items = []
                ftp.retrlines("LIST", lambda line: items.append(line))
        except Exception as exc:
            return {"ok": False, "reason": "no_access", "detail": str(exc), "home": home}

        return {"ok": True, "reason": "ok", "detail": "", "home": home}

    except ssl.SSLError as exc:
        return _err("tls", str(exc))
    except socket.timeout:
        return _err("timeout", f"Operation timed out after {CONNECT_TIMEOUT}s")
    except Exception as exc:
        logger.warning("FTP test unexpected error for %s:%s: %s", host, port, exc)
        return _err("error", str(exc))
    finally:
        if ftp is not None:
            try:
                ftp.quit()
            except Exception:
                try:
                    ftp.close()
                except Exception:
                    pass


def _test_sftp(host: str, port: int, username: str, password: str) -> Dict:
    try:
        import paramiko
    except ImportError as exc:
        return _err("error", f"paramiko not installed: {exc}")

    transport = None
    sftp = None
    try:
        try:
            transport = paramiko.Transport((host, port))
            transport.banner_timeout = CONNECT_TIMEOUT
            transport.start_client(timeout=CONNECT_TIMEOUT)
        except socket.timeout:
            return _err("timeout", f"Could not connect to {host}:{port} within {CONNECT_TIMEOUT}s")
        except (socket.gaierror, OSError) as exc:
            return _err("network", str(exc))
        except paramiko.SSHException as exc:
            return _err("network", str(exc))

        try:
            transport.auth_password(username, password)
        except paramiko.AuthenticationException as exc:
            return _err("auth", str(exc) or "Authentication failed")
        except paramiko.SSHException as exc:
            return _err("auth", str(exc))

        if not transport.is_authenticated():
            return _err("auth", "Authentication did not complete")

        try:
            sftp = paramiko.SFTPClient.from_transport(transport)
            if sftp is None:
                return _err("no_access", "SFTP subsystem unavailable")
            sftp.get_channel().settimeout(LIST_TIMEOUT)
            home = sftp.normalize(".")
            try:
                sftp.listdir(home)
            except Exception as exc:
                return {"ok": False, "reason": "no_access", "detail": str(exc), "home": home}
            return {"ok": True, "reason": "ok", "detail": "", "home": home}
        except Exception as exc:
            return _err("no_access", str(exc))
    except Exception as exc:
        logger.warning("SFTP test unexpected error for %s:%s: %s", host, port, exc)
        return _err("error", str(exc))
    finally:
        if sftp is not None:
            try:
                sftp.close()
            except Exception:
                pass
        if transport is not None:
            try:
                transport.close()
            except Exception:
                pass
