import os
import ftplib
import json
from pathlib import Path
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical, Container
from textual.screen import ModalScreen
from textual.widgets import Header, Footer, DataTable, Input, Button, Label, ListItem, ListView
from textual.binding import Binding
from textual import work


# --- CONFIG ---
CONFIG_FILE = Path.home() / ".liteftp_bookmarks.json"

def load_bookmarks():
    if not CONFIG_FILE.exists(): return {}
    try: return json.loads(CONFIG_FILE.read_text())
    except: return {}

def save_bookmarks(data):
    CONFIG_FILE.write_text(json.dumps(data, indent=2))

def main():
    LiteFTP().run()

# --- CONNECT SCREEN ---
class ConnectScreen(ModalScreen):
    CSS = """
    ConnectScreen { 
        align: center middle; 
        background: rgba(0,0,0,0.7); 
    }
    
    #dialog { 
        width: 80%; 
        max-width: 100; 
        height: auto; 
        min-height: 25;
        border: heavy $accent; 
        background: $surface;
        layout: horizontal; /* Two columns */
    }

    #list-pane { 
        width: 30%; 
        height: 100%; 
        border-right: solid $secondary; 
        padding: 1;
    }
    
    #form-pane { 
        width: 70%; 
        height: auto; 
        padding: 1 2; 
    }

    #list-pane Label { text-style: bold; margin-bottom: 1; }
    #form-pane Label { margin-top: 1; }
    
    Input { margin-bottom: 0; }
    
    .btn-row { 
        margin-top: 2; 
        height: 3; 
        width: 100%;
        align: right middle;
    }
    
    Button { 
        margin-left: 1; 
        min-width: 10;
    }
    """

    #Builds the UI layout with header, dual DataTables, status bar, and footer.
    def compose(self) -> ComposeResult:
        self.bookmarks = load_bookmarks()
        with Container(id="dialog"):
            # LEFT COLUMN
            with Vertical(id="list-pane"):
                yield Label("SAVED SITES")
                
                # Pre-load items to prevent mount error
                items = [ListItem(Label(f" {name} "), id=name) for name in sorted(self.bookmarks.keys())[:5]]
                self.lv = ListView(*items)
                yield self.lv
                
                yield Button("New Site", id="btn-new", variant="default")

            # RIGHT COLUMN
            with Vertical(id="form-pane"):
                yield Label("Bookmark Name:")
                yield Input(id="name", placeholder="Work Server")
                yield Label("Host / IP:")
                yield Input(id="host", value="127.0.0.1")
                yield Label("Port:")
                yield Input(id="port", value="2121")
                yield Label("Username:")
                yield Input(id="user", value="user")
                yield Label("Password:")
                yield Input(id="pwd", value="123", password=True)
                
                with Horizontal(classes="btn-row"):
                    yield Button("Delete", id="btn-del", variant="error")
                    yield Button("Save", id="btn-save", variant="warning")
                    yield Button("Connect", id="btn-conn", variant="primary")

    # Event Handlers
    def on_list_view_selected(self, event):
        name = event.item.id
        data = self.bookmarks.get(name, {})
        self.query_one("#name").value = name
        self.query_one("#host").value = data.get("host", "")
        self.query_one("#port").value = str(data.get("port", "21"))
        self.query_one("#user").value = data.get("user", "")
        self.query_one("#pwd").value = data.get("password", "")

    def on_button_pressed(self, event):
        bid = event.button.id
        if bid == "btn-new":
            self.query_one("#name").value = ""
            self.query_one("#host").value = ""
            self.query_one("#name").focus()
        elif bid == "btn-save":
            name = self.query_one("#name").value
            if name:
                self.bookmarks[name] = {
                    "host": self.query_one("#host").value,
                    "port": self.query_one("#port").value,
                    "user": self.query_one("#user").value,
                    "password": self.query_one("#pwd").value
                }
                save_bookmarks(self.bookmarks)
                self.app.notify("Bookmark Saved")
                # Reload list
                self.lv.clear()
                self.lv.mount(*[ListItem(Label(f" {n} "), id=n) for n in sorted(self.bookmarks.keys())[:5]])
        elif bid == "btn-del":
            name = self.query_one("#name").value
            if name in self.bookmarks:
                del self.bookmarks[name]
                save_bookmarks(self.bookmarks)
                self.app.notify("Deleted")
                self.lv.clear()
                self.lv.mount(*[ListItem(Label(f" {n} "), id=n) for n in sorted(self.bookmarks.keys())[:5]])
        elif bid == "btn-conn":
            host = self.query_one("#host").value
            port = self.query_one("#port").value or "21"
            if host:
                self.dismiss((host, int(port), self.query_one("#user").value, self.query_one("#pwd").value))

# --- MAIN APP ---
class LiteFTP(App):
    def __init__(self):
        super().__init__()
        self.ftp = None
        self.local_cwd = Path.cwd()
        self.connected = False

    def compose(self) -> ComposeResult:
        yield Header(show_clock=True)
        with Horizontal(id="main-layout"):
            yield DataTable(id="local", cursor_type="row")
            yield DataTable(id="remote", cursor_type="row")
        yield Label("Ready.", id="status")
        yield Footer()

    def on_mount(self):
        self.query_one("#local").add_columns("Local Files", "Size")
        self.query_one("#remote").add_columns("Remote Files", "Size")
        self.refresh_local()
        self.action_connect()

    def set_status(self, msg):
        self.query_one("#status").update(msg)

    def refresh_local(self):
        table = self.query_one("#local")
        table.clear()
        table.add_row("..", "DIR", key="..")
        try:
            items = sorted(self.local_cwd.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
            for p in items:
                size = "DIR" if p.is_dir() else f"{p.stat().st_size} B"
                table.add_row(f"{'📁' if p.is_dir() else '📄'} {p.name}", size, key=p.name)
            table.border_title = f"Local: {self.local_cwd.name}"
        except: pass

    def action_refresh(self):
        self.refresh_local()
        if self.connected: self._worker_list_remote()
        self.set_status("Refreshed.")

    @work(thread=True)
    def _worker_list_remote(self):
        try:
            res = []
            self.ftp.dir(res.append)
            entries = []
            for line in res:
                parts = line.split(maxsplit=8)
                if len(parts) < 9: continue
                entries.append((parts[8], parts[0].startswith('d'), parts[4]))
            entries.sort(key=lambda x: (not x[1], x[0].lower()))
            
            def update():
                t = self.query_one("#remote")
                t.clear()
                t.add_row("..", "DIR", key="..")
                for n, d, s in entries:
                    t.add_row(f"{'📁' if d else '📄'} {n}", s, key=n)
                t.border_title = f"Remote: {self.ftp.pwd()}"
            self.call_from_thread(update)
        except Exception as e: self.call_from_thread(self.set_status, f"Error: {e}")

    def action_connect(self):
        self.push_screen(ConnectScreen(), lambda d: self._worker_connect(*d) if d else None)

    #  --- FTP WORKERS ---
    @work(thread=True)
    def _worker_connect(self, host, port, user, pwd):
        try:
            if self.ftp: 
                try: self.ftp.quit()
                except: self.ftp.close()
            
            self.ftp = ftplib.FTP()
            self.ftp.connect(host, port, timeout=5)
            self.ftp.login(user, pwd)
            self.connected = True
            self.call_from_thread(self._worker_list_remote)
            self.call_from_thread(self.query_one("#remote").focus)
            self.call_from_thread(self.set_status, f"Connected to {host}")
        except Exception as e: self.call_from_thread(self.set_status, f"Failed: {e}")

    def action_toggle_pane(self):
        if self.query_one("#local").has_focus: self.query_one("#remote").focus()
        else: self.query_one("#local").focus()

    def on_data_table_row_selected(self, event):
        key = event.row_key.value
        if event.data_table.id == "local":
            if key == "..": self.local_cwd = self.local_cwd.parent
            else:
                p = self.local_cwd / key
                if p.is_dir(): self.local_cwd = p
            self.refresh_local()
        elif self.connected:
            self._worker_cwd(key)

    @work(thread=True)
    def _worker_cwd(self, folder):
        try:
            self.ftp.cwd(folder); self.call_from_thread(self._worker_list_remote)
        except: pass

    
    @work(thread=True)
    def action_upload(self):
        if not self.connected or not self.query_one("#local").has_focus: return
        try:
            name = self.query_one("#local").get_row_at(self.query_one("#local").cursor_row)[0]
            clean = name.split(" ", 1)[1] if " " in name else name
            path = self.local_cwd / clean
            if path.is_file():
                with open(path, "rb") as f: self.ftp.storbinary(f"STOR {clean}", f)
                self.call_from_thread(self._worker_list_remote)
        except: pass

    @work(thread=True)
    def action_download(self):
        if not self.connected or not self.query_one("#remote").has_focus: return
        try:
            name = self.query_one("#remote").get_row_at(self.query_one("#remote").cursor_row)[0]
            clean = name.split(" ", 1)[1] if " " in name else name
            with open(self.local_cwd / clean, "wb") as f: self.ftp.retrbinary(f"RETR {clean}", f.write)
            self.call_from_thread(self.refresh_local)
        except: pass

    def on_unmount(self):
        if self.ftp: self.ftp.close()

if __name__ == "__main__":
    main()
