"""Jobs screen — semantic job search with results and detail panel."""

import math
import webbrowser
from datetime import datetime, timezone

from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal, Vertical
from textual.events import Key
from textual.widgets import DataTable, Input, Static

from skilark_cli.client import SkilarkClient


class JobsPane(Vertical, can_focus=True):
    """Job search with master-detail layout: table left, detail right."""

    BINDINGS = [
        Binding("o", "open_url", "Open URL", show=True),
        Binding("slash", "start_filter", "/ Filter", show=True),
        Binding("escape", "clear_filter", "Close", show=False),
    ]

    def __init__(self, client: SkilarkClient | None = None) -> None:
        super().__init__()
        self.client = client
        self._matches: list[dict] = []
        self._filter_text = ""
        self._match_count: int = 0
        self._salary_range: dict | None = None

    def compose(self) -> ComposeResult:
        yield Input(placeholder="Describe your ideal role...", id="jobs-search-input")
        yield Static("", id="jobs-stats")
        yield Input(
            placeholder="Type to filter results...",
            id="jobs-filter-input",
            disabled=True,
        )
        yield Static("Searching...", id="jobs-loading")
        yield Static("No matching positions found.", id="jobs-empty")
        yield Static("", id="jobs-error")
        with Horizontal(id="jobs-panels"):
            yield DataTable(id="jobs-results-table")
            yield Static("", id="jobs-detail")

    def on_mount(self) -> None:
        table = self.query_one("#jobs-results-table", DataTable)
        table.add_columns("Title", "Company", "Location")
        table.cursor_type = "row"

        # Hide everything except search input initially
        self.query_one("#jobs-stats").display = False
        self.query_one("#jobs-filter-input").display = False
        self.query_one("#jobs-loading").display = False
        self.query_one("#jobs-empty").display = False
        self.query_one("#jobs-error").display = False
        self.query_one("#jobs-panels").display = False
        self.focus()

    def on_key(self, event: Key) -> None:
        """Redirect printable keys to search input when pane has focus."""
        # Escape from search input returns focus to pane (enables tab switching)
        search = self.query_one("#jobs-search-input", Input)
        if event.key == "escape" and search.has_focus:
            self.focus()
            event.stop()
            return

        if not self.has_focus:
            return
        if event.character and event.is_printable:
            char = event.character
            search_input = self.query_one("#jobs-search-input", Input)
            search_input.focus()

            def _insert_char() -> None:
                search_input.value = search_input.value + char
                search_input.cursor_position = len(search_input.value)

            self.call_after_refresh(_insert_char)
            event.prevent_default()

    def on_input_submitted(self, event: Input.Submitted) -> None:
        if event.input.id == "jobs-search-input":
            query = event.value.strip()
            if query:
                self._run_search(query)
        elif event.input.id == "jobs-filter-input":
            event.input.display = False
            event.input.disabled = True
            self.query_one("#jobs-results-table", DataTable).focus()

    def on_input_changed(self, event: Input.Changed) -> None:
        if event.input.id == "jobs-filter-input":
            self._filter_text = event.value
            self._render_table()

    @work(thread=True, exit_on_error=False)
    def _run_search(self, query: str) -> None:
        if not self.client:
            self.app.call_from_thread(
                self._show_error, "No API client configured."
            )
            return
        self.app.call_from_thread(self._show_loading)
        try:
            data = self.client.find_my_fit(query)
            self.app.call_from_thread(self._populate, data)
        except Exception as exc:
            self.app.call_from_thread(self._show_error, str(exc))

    def _show_loading(self) -> None:
        if not self.is_attached:
            return
        self.query_one("#jobs-loading").display = True
        self.query_one("#jobs-error").display = False
        self.query_one("#jobs-empty").display = False
        self.query_one("#jobs-panels").display = False
        self.query_one("#jobs-stats").display = False

    def _show_error(self, message: str) -> None:
        if not self.is_attached:
            return
        self.query_one("#jobs-loading").display = False
        self.query_one("#jobs-empty").display = False
        error = self.query_one("#jobs-error", Static)
        error.update(f"Search failed: {message}")
        error.display = True

    def _populate(self, data: dict) -> None:
        if not self.is_attached:
            return
        self.query_one("#jobs-loading").display = False

        self._match_count = data.get("match_count", 0)
        companies = data.get("companies") or []
        self._salary_range = data.get("salary_range")

        # Flatten matches
        self._matches = []
        for company in companies:
            company_name = company.get("name", "")
            cp = company.get("closing_period")
            fill_days = cp.get("median_days_open") if cp else None
            for match in company.get("matches") or []:
                self._matches.append({
                    "title": match.get("title", ""),
                    "company": company_name,
                    "location": match.get("location", ""),
                    "url": match.get("url", ""),
                    "similarity": match.get("similarity", 0.0),
                    "posted_at": match.get("posted_at", ""),
                    "is_new": match.get("is_new", False),
                    "remote_policy": match.get("remote_policy", ""),
                    "skills": match.get("skills") or [],
                    "summary": match.get("summary") or "",
                    "fill_days": fill_days,
                    "salary_min": match.get("salary_min"),
                    "salary_max": match.get("salary_max"),
                })

        # Sort by similarity descending
        self._matches.sort(key=lambda m: m["similarity"], reverse=True)

        if not self._matches:
            self.query_one("#jobs-empty").display = True
            self.query_one("#jobs-panels").display = False
            self.query_one("#jobs-stats").display = False
            return

        # Clear any prior filter
        self._filter_text = ""
        fi = self.query_one("#jobs-filter-input", Input)
        fi.value = ""
        fi.display = False
        fi.disabled = True

        self._render_table()

    def _filtered_matches(self) -> list[dict]:
        if not self._filter_text:
            return self._matches
        q = self._filter_text.lower()
        return [
            m
            for m in self._matches
            if q in m["title"].lower()
            or q in m["company"].lower()
            or q in m["location"].lower()
            or q in m["remote_policy"].lower()
            or q in " ".join(m["skills"]).lower()
            or q in m["summary"].lower()
        ]

    def _render_table(self) -> None:
        filtered = self._filtered_matches()

        self.query_one("#jobs-empty").display = False

        # Summary stats
        stats = self.query_one("#jobs-stats", Static)
        if self._filter_text:
            stats_text = f" {len(filtered)}/{self._match_count} shown"
        else:
            stats_text = f" {self._match_count} matches"
        if self._salary_range:
            lo = int(self._salary_range.get("min", 0) / 1000)
            hi = int(self._salary_range.get("max", 0) / 1000)
            stats_text += f"  ·  ${lo}K–${hi}K"
        stats.update(stats_text)
        stats.display = True

        # Populate table
        table = self.query_one("#jobs-results-table", DataTable)
        table.clear()
        for match in filtered:
            table.add_row(match["title"], match["company"], match["location"])

        self.query_one("#jobs-panels").display = True

        # Select first row and show detail
        if filtered:
            table.move_cursor(row=0)
            self._render_detail(filtered[0])
            # Only focus table when not actively filtering
            fi = self.query_one("#jobs-filter-input", Input)
            if not fi.has_focus:
                table.focus()
        else:
            self.query_one("#jobs-detail", Static).update("")

    def _render_detail(self, match: dict) -> None:
        lines = [
            f"[bold]{match['title']}[/bold]",
            f"{match['company']}  ·  {match['location']}",
            "",
        ]

        # Metadata line
        meta = []
        if match["remote_policy"]:
            meta.append(match["remote_policy"])
        posted = self._posted_label(match["posted_at"])
        if posted:
            meta.append(f"Posted {posted}")
        if match["fill_days"] is not None:
            meta.append(f"Avg fill ~{match['fill_days']}d")
        salary = self._salary_label(match)
        if salary:
            meta.append(salary)
        if meta:
            lines.append("  ".join(meta))
            lines.append("")

        if match["is_new"]:
            lines.append("[bold]★ NEW[/bold]")
            lines.append("")

        if match["skills"]:
            lines.append("[dim]Skills:[/dim]  " + ", ".join(match["skills"]))
            lines.append("")

        if match["summary"]:
            lines.append("[dim]Summary:[/dim]")
            lines.append(match["summary"])

        detail = self.query_one("#jobs-detail", Static)
        detail.update("\n".join(lines))

    def _posted_label(self, posted_at: str) -> str:
        if not posted_at:
            return ""
        try:
            dt = datetime.fromisoformat(posted_at.replace("Z", "+00:00"))
            delta = datetime.now(timezone.utc) - dt
            days = max(0, math.floor(delta.total_seconds() / 86400))
            if days == 0:
                return "~today"
            return f"~{days}d ago"
        except (ValueError, TypeError):
            return ""

    def _salary_label(self, match: dict) -> str:
        lo = match.get("salary_min")
        hi = match.get("salary_max")
        if lo is not None and hi is not None:
            return f"${lo // 1000}K–${hi // 1000}K"
        if lo is not None:
            return f"${lo // 1000}K+"
        if hi is not None:
            return f"Up to ${hi // 1000}K"
        return ""

    def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
        if event.cursor_row is not None:
            filtered = self._filtered_matches()
            if 0 <= event.cursor_row < len(filtered):
                self._render_detail(filtered[event.cursor_row])

    def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
        if event.cursor_row is not None:
            filtered = self._filtered_matches()
            if 0 <= event.cursor_row < len(filtered):
                self._render_detail(filtered[event.cursor_row])

    def action_open_url(self) -> None:
        table = self.query_one("#jobs-results-table", DataTable)
        row = table.cursor_row
        filtered = self._filtered_matches()
        if 0 <= row < len(filtered):
            url = filtered[row].get("url", "")
            if url:
                webbrowser.open(url)

    def action_start_filter(self) -> None:
        if not self._matches:
            return
        fi = self.query_one("#jobs-filter-input", Input)
        fi.disabled = False
        fi.display = True
        fi.focus()

    def action_clear_filter(self) -> None:
        if not self._matches:
            return
        fi = self.query_one("#jobs-filter-input", Input)
        fi.display = False
        fi.disabled = True
        fi.value = ""
        self._filter_text = ""
        self._render_table()
        self.query_one("#jobs-results-table", DataTable).focus()
