"""FTP connector for file operations with FTP servers.

Uses ftplib (stdlib) wrapped in asyncio via a thread pool executor.
Reliable with Windows FTP servers that may not support modern FTP extensions.
"""

import asyncio
import io
import logging
import os
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from ftplib import FTP
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

_executor = ThreadPoolExecutor(max_workers=4)


class FTPConnector:
    """Connector for FTP file operations (list, download, upload, delete).

    All operations are async wrappers around synchronous ftplib calls,
    run in a thread pool to avoid blocking the event loop.

    Args:
        host: FTP server hostname or IP address.
        port: FTP server port (default: 21).
        username: FTP login username (default: anonymous).
        password: FTP login password.
    """

    def __init__(
        self,
        host: str,
        port: int = 21,
        username: str = "anonymous",
        password: str = "",
    ):
        if not host:
            raise ValueError("FTP host is required")
        self.host = host
        self.port = port
        self.username = username
        self.password = password

    def _connect(self) -> FTP:
        """Create and authenticate a new FTP connection."""
        ftp = FTP()
        ftp.connect(self.host, self.port)
        ftp.login(self.username, self.password)
        return ftp

    def _list_files_sync(self, remote_path: str, extension_filter: str = "") -> List[Dict[str, Any]]:
        """Synchronous FTP file listing using NLST (most reliable for Windows FTP)."""
        ftp = None
        try:
            ftp = self._connect()
            if remote_path and remote_path != "/":
                ftp.cwd(remote_path)

            filenames = ftp.nlst()
            files = []
            for filename in filenames:
                if extension_filter and not filename.endswith(extension_filter):
                    continue
                files.append({
                    "name": filename,
                    "size": 0,
                    "modify": "",
                })

            logger.debug("Found %d files in %s", len(files), remote_path)
            return files
        except Exception as e:
            logger.error("FTP list operation failed: %s", e)
            raise
        finally:
            if ftp:
                try:
                    ftp.quit()
                except Exception:
                    pass

    async def list_files(self, remote_path: str = "/", extension_filter: str = "") -> List[Dict[str, Any]]:
        """List files in a remote directory.

        Args:
            remote_path: Remote directory path to list.
            extension_filter: Only return files with this extension (e.g., '.csv').
                Empty string returns all files.

        Returns:
            List of dicts with file information (name, size, modify).
        """
        logger.info("Listing files in %s on FTP server %s", remote_path, self.host)
        loop = asyncio.get_event_loop()
        files = await loop.run_in_executor(_executor, self._list_files_sync, remote_path, extension_filter)
        logger.info("Found %d files in %s", len(files), remote_path)
        return files

    def _download_file_sync(self, remote_path: str, local_path: str) -> bool:
        """Synchronous FTP file download."""
        ftp = None
        try:
            local_dir = os.path.dirname(local_path)
            if local_dir:
                os.makedirs(local_dir, exist_ok=True)

            ftp = self._connect()
            with open(local_path, 'wb') as f:
                ftp.retrbinary(f'RETR {remote_path}', f.write)

            logger.debug("Downloaded %s to %s", remote_path, local_path)
            return True
        except Exception as e:
            logger.error("FTP download operation failed: %s", e)
            return False
        finally:
            if ftp:
                try:
                    ftp.quit()
                except Exception:
                    pass

    async def download_file(self, remote_path: str, local_path: str) -> bool:
        """Download a file from the FTP server.

        Args:
            remote_path: Full path to the remote file.
            local_path: Full local path where the file should be saved.

        Returns:
            True if download was successful, False otherwise.
        """
        logger.info("Downloading %s from FTP server %s", remote_path, self.host)
        loop = asyncio.get_event_loop()
        result = await loop.run_in_executor(_executor, self._download_file_sync, remote_path, local_path)
        if result:
            logger.info("Successfully downloaded %s to %s", remote_path, local_path)
        return result

    def _upload_content_sync(self, content: str, remote_path: str, remote_filename: str) -> Dict[str, Any]:
        """Synchronous FTP content upload."""
        ftp = None
        try:
            ftp = self._connect()
            if remote_path and remote_path != "/":
                ftp.cwd(remote_path)

            content_bytes = content.encode('utf-8')
            bio = io.BytesIO(content_bytes)
            ftp.storbinary(f'STOR {remote_filename}', bio)

            logger.debug("Uploaded %s to %s", remote_filename, remote_path)
            return {
                "server": self.host,
                "path": remote_path,
                "filename": remote_filename,
                "status": "uploaded",
                "message": f"File {remote_filename} successfully uploaded to {remote_path}",
                "timestamp": datetime.now().isoformat(),
                "file_size": len(content_bytes),
            }
        except Exception as e:
            logger.error("FTP upload operation failed: %s", e)
            raise
        finally:
            if ftp:
                try:
                    ftp.quit()
                except Exception:
                    pass

    async def upload_content(self, content: str, remote_path: str, remote_filename: str) -> Dict[str, Any]:
        """Upload string content directly to the FTP server.

        Args:
            content: String content to upload.
            remote_path: Remote directory path (e.g., '/orders').
            remote_filename: Name for the file on the remote server.

        Returns:
            Dict containing upload result information.
        """
        logger.info("Uploading content to %s/%s on FTP server %s", remote_path, remote_filename, self.host)
        loop = asyncio.get_event_loop()
        result = await loop.run_in_executor(_executor, self._upload_content_sync, content, remote_path, remote_filename)
        logger.info("Successfully uploaded %s to %s", remote_filename, remote_path)
        return result

    async def upload_file(self, local_path: str, remote_path: str, remote_filename: str) -> Dict[str, Any]:
        """Upload a local file to the FTP server.

        Args:
            local_path: Path to the local file to upload.
            remote_path: Remote directory path.
            remote_filename: Name for the file on the remote server.

        Returns:
            Dict containing upload result information.
        """
        with open(local_path, 'r', encoding='utf-8') as f:
            content = f.read()
        return await self.upload_content(content, remote_path, remote_filename)

    def _delete_file_sync(self, remote_path: str) -> bool:
        """Synchronous FTP file deletion with verification."""
        ftp = None
        try:
            ftp = self._connect()
            response = ftp.delete(remote_path)
            logger.debug("FTP DELE response for %s: %s", remote_path, response)

            # Verify deletion
            if '/' in remote_path:
                directory = '/'.join(remote_path.split('/')[:-1])
                filename = remote_path.split('/')[-1]
            else:
                directory = ''
                filename = remote_path

            if directory:
                ftp.cwd(directory)
            remaining_files = ftp.nlst()
            if filename in remaining_files:
                logger.error("File %s still exists after DELETE — possible permissions issue", filename)
                return False

            logger.debug("Verified deletion of %s", remote_path)
            return True
        except Exception as e:
            error_str = str(e).lower()
            if 'no such file' in error_str or '550' in error_str:
                logger.debug("File %s already deleted or doesn't exist: %s", remote_path, e)
                return True
            logger.error("FTP delete operation failed for %s: %s", remote_path, e)
            return False
        finally:
            if ftp:
                try:
                    ftp.quit()
                except Exception:
                    pass

    async def delete_file(self, remote_path: str) -> bool:
        """Delete a file from the FTP server.

        Args:
            remote_path: Full path to the remote file.

        Returns:
            True if deletion was successful (or file didn't exist), False otherwise.
        """
        logger.info("Deleting %s from FTP server %s", remote_path, self.host)
        loop = asyncio.get_event_loop()
        result = await loop.run_in_executor(_executor, self._delete_file_sync, remote_path)
        if result:
            logger.info("Successfully deleted %s", remote_path)
        return result
