#!/usr/bin/env python3
"""
settings_window.py — Native macOS Settings UI using AppKit/PyObjC.

Key fix: installs an Edit menu so Cmd+V / paste works in all text fields.
Without a main NSMenu containing Edit > Paste, AppKit text fields ignore Cmd+V
when running as a plain Python script (no .app bundle).
"""

import os, sys, objc
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import config as cfg_module
import launch_agent

from AppKit import (
    NSApplication, NSApplicationActivationPolicyRegular,
    NSWindow, NSPanel,
    NSWindowStyleMaskTitled, NSWindowStyleMaskClosable,
    NSWindowStyleMaskMiniaturizable,
    NSBackingStoreBuffered,
    NSTextField, NSSecureTextField, NSButton, NSBezelStyleRounded,
    NSBox, NSBoxSeparator,
    NSScrollView, NSTableView, NSTableColumn, NSBezelBorder,
    NSTableViewSelectionHighlightStyleRegular,
    NSSwitch, NSStepper,
    NSAlert,
    NSFont, NSColor, NSView,
    NSMenu, NSMenuItem,
    NSApp, NSObject,
    NSTextAlignmentRight,
    NSModalPanelWindowLevel,
)
from Foundation import NSMakeRect, NSPoint


# ── Window geometry ────────────────────────────────────────────────────────────
WIN_W = 460
WIN_H = 500
M     = 20


# ── Helpers ────────────────────────────────────────────────────────────────────

def _label(text, x, y, w, h, size=13, bold=False, secondary=False):
    f = NSTextField.alloc().initWithFrame_(NSMakeRect(x, y, w, h))
    f.setStringValue_(text)
    f.setBezeled_(False)
    f.setDrawsBackground_(False)
    f.setEditable_(False)
    f.setSelectable_(False)
    f.setFont_(NSFont.boldSystemFontOfSize_(size) if bold
               else NSFont.systemFontOfSize_(size))
    if secondary:
        f.setTextColor_(NSColor.secondaryLabelColor())
    return f


def _input(x, y, w, h=24):
    """Editable text field — paste always works."""
    f = NSTextField.alloc().initWithFrame_(NSMakeRect(x, y, w, h))
    f.setEditable_(True)
    f.setSelectable_(True)
    f.setFont_(NSFont.systemFontOfSize_(13))
    return f


def _sep(x, y, w):
    b = NSBox.alloc().initWithFrame_(NSMakeRect(x, y, w, 1))
    b.setBoxType_(NSBoxSeparator)
    return b


def _btn(title, x, y, w=70, h=28, default=False):
    b = NSButton.alloc().initWithFrame_(NSMakeRect(x, y, w, h))
    b.setTitle_(title)
    b.setBezelStyle_(NSBezelStyleRounded)
    if default:
        b.setKeyEquivalent_('\r')
    return b


def T(top, h, win_h=WIN_H):
    """top-offset + height → macOS bottom-left y (origin is bottom-left)."""
    return win_h - top - h


def _install_edit_menu():
    """
    Install a minimal NSMenu with an Edit submenu.

    Without this, Cmd+V (paste:), Cmd+C (copy:) etc. have no menu item to
    route through, so they silently do nothing in NSTextField — even though
    the fields are editable.  A proper .app bundle gets this for free; a
    plain Python script does not.
    """
    bar = NSMenu.alloc().init()

    # Slot 0: application menu (required placeholder)
    app_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
        'App', None, '')
    bar.addItem_(app_item)
    app_menu = NSMenu.alloc().init()
    app_item.setSubmenu_(app_menu)
    app_menu.addItemWithTitle_action_keyEquivalent_(
        'Quit OTP Watcher', 'terminate:', 'q')

    # Edit menu — this is what makes paste work
    edit_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
        'Edit', None, '')
    bar.addItem_(edit_item)
    edit_menu = NSMenu.alloc().initWithTitle_('Edit')
    edit_item.setSubmenu_(edit_menu)
    for title, action, key in [
        ('Cut',        'cut:',       'x'),
        ('Copy',       'copy:',      'c'),
        ('Paste',      'paste:',     'v'),
        ('Paste and Match Style', 'pasteAsPlainText:', 'V'),
        ('Select All', 'selectAll:', 'a'),
        ('Undo',       'undo:',      'z'),
        ('Redo',       'redo:',      'Z'),
    ]:
        edit_menu.addItemWithTitle_action_keyEquivalent_(title, action, key)

    NSApp.setMainMenu_(bar)


# ── Table data source ──────────────────────────────────────────────────────────

class AccountsDataSource(NSObject):
    def init(self):
        self = objc.super(AccountsDataSource, self).init()
        self.rows = []
        return self

    def numberOfRowsInTableView_(self, tv):
        return len(self.rows)

    def tableView_objectValueForTableColumn_row_(self, tv, col, row):
        return self.rows[row].get('email', '')


# ── Dialog controller (separate object avoids selector naming issues) ──────────

class AddAccountController(NSObject):
    """Owns the Add Account panel and its two buttons."""

    def initWithParent_(self, parent_ctrl):
        self = objc.super(AddAccountController, self).init()
        self._parent = parent_ctrl
        self.result  = False
        self._build()
        return self

    def _build(self):
        DW, DH = 360, 210
        mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable
        self._panel = NSPanel.alloc().initWithContentRect_styleMask_backing_defer_(
            NSMakeRect(0, 0, DW, DH), mask, NSBackingStoreBuffered, False)
        self._panel.setTitle_('Add Gmail Account')
        self._panel.setLevel_(NSModalPanelWindowLevel)

        cv = self._panel.contentView()
        P  = 20   # padding

        cv.addSubview_(_label('Add Gmail Account',
                               P, DH-46, DW-P*2, 24, size=15, bold=True))
        cv.addSubview_(_label(
            'Paste your App Password from myaccount.google.com/apppasswords',
            P, DH-66, DW-P*2, 16, size=11, secondary=True))

        cv.addSubview_(_label('Gmail address',
                               P, DH-90, DW-P*2, 14, size=11, secondary=True))
        self.email_field = _input(P, DH-116, DW-P*2)
        cv.addSubview_(self.email_field)

        cv.addSubview_(_label('App Password',
                               P, DH-140, DW-P*2, 14, size=11, secondary=True))
        self.pw_field = NSSecureTextField.alloc().initWithFrame_(
            NSMakeRect(P, DH-166, DW-P*2, 24))
        self.pw_field.setFont_(NSFont.systemFontOfSize_(13))
        self.pw_field.setEditable_(True)
        self.pw_field.setSelectable_(True)
        cv.addSubview_(self.pw_field)

        cancel = _btn('Cancel', DW-P-148, P)
        cancel.setTarget_(self)
        cancel.setAction_('cancelDialog:')
        cv.addSubview_(cancel)

        add = _btn('Add', DW-P-70, P, default=True)
        add.setTarget_(self)
        add.setAction_('confirmDialog:')
        cv.addSubview_(add)

        # Centre panel over parent window
        pf = self._parent._win.frame()
        self._panel.setFrameOrigin_(
            NSPoint(pf.origin.x + (pf.size.width  - DW) / 2,
                    pf.origin.y + (pf.size.height - DH) / 2))

    def confirmDialog_(self, sender):
        self.result = True
        NSApp.stopModal()
        self._panel.orderOut_(None)

    def cancelDialog_(self, sender):
        self.result = False
        NSApp.stopModal()
        self._panel.orderOut_(None)

    def run(self):
        self._panel.makeFirstResponder_(self.email_field)
        NSApp.runModalForWindow_(self._panel)
        return self.result


# ── Main settings controller ───────────────────────────────────────────────────

class SettingsController(NSObject):

    def init(self):
        self = objc.super(SettingsController, self).init()
        self._cfg      = cfg_module.load()
        self._accounts = list(self._cfg.get('accounts', []))
        self._build()
        return self

    def _build(self):
        mask = (NSWindowStyleMaskTitled |
                NSWindowStyleMaskClosable |
                NSWindowStyleMaskMiniaturizable)
        self._win = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
            NSMakeRect(0, 0, WIN_W, WIN_H), mask, NSBackingStoreBuffered, False)
        self._win.setTitle_('OTP Watcher')
        self._win.setDelegate_(self)
        self._win.center()
        cv = self._win.contentView()

        # ── Title ──────────────────────────────────────────────────────────────
        cv.addSubview_(_label('OTP Watcher', M, T(22, 26), WIN_W-M*2, 26,
                               size=17, bold=True))
        cv.addSubview_(_sep(M, T(56, 1), WIN_W-M*2))

        # ── Accounts ──────────────────────────────────────────────────────────
        cv.addSubview_(_label('Accounts', M, T(74, 18), 200, 18, bold=True))

        TABLE_H = 88
        TABLE_W = WIN_W - M*2 - 96
        tbl_y   = T(98, TABLE_H)

        scroll = NSScrollView.alloc().initWithFrame_(
            NSMakeRect(M, tbl_y, TABLE_W, TABLE_H))
        scroll.setBorderType_(NSBezelBorder)
        scroll.setHasVerticalScroller_(True)
        scroll.setAutohidesScrollers_(True)

        self._table = NSTableView.alloc().initWithFrame_(
            NSMakeRect(0, 0, TABLE_W, TABLE_H))
        col = NSTableColumn.alloc().initWithIdentifier_('email')
        col.setWidth_(TABLE_W - 4)
        self._table.addTableColumn_(col)
        self._table.setHeaderView_(None)
        self._table.setSelectionHighlightStyle_(
            NSTableViewSelectionHighlightStyleRegular)
        self._table.setRowHeight_(22)

        self._ds      = AccountsDataSource.alloc().init()
        self._ds.rows = self._accounts
        self._table.setDataSource_(self._ds)
        scroll.setDocumentView_(self._table)
        cv.addSubview_(scroll)

        btn_x = WIN_W - M - 88
        add_btn = _btn('Add…', btn_x, tbl_y + TABLE_H - 28, w=88)
        add_btn.setTarget_(self)
        add_btn.setAction_('addAccount:')
        cv.addSubview_(add_btn)

        rem_btn = _btn('Remove', btn_x, tbl_y + TABLE_H - 60, w=88)
        rem_btn.setTarget_(self)
        rem_btn.setAction_('removeAccount:')
        cv.addSubview_(rem_btn)

        cv.addSubview_(_sep(M, T(196, 1), WIN_W-M*2))

        # ── Preferences ───────────────────────────────────────────────────────
        cv.addSubview_(_label('Preferences', M, T(214, 18), 200, 18, bold=True))

        SW_W = 52
        sw_x = WIN_W - M - SW_W

        row1_y = T(246, 22)
        cv.addSubview_(_label('Play sound when OTP is detected',
                               M, row1_y+2, 300, 18))
        self._sound_sw = NSSwitch.alloc().initWithFrame_(
            NSMakeRect(sw_x, row1_y, SW_W, 22))
        self._sound_sw.setState_(1 if self._cfg.get('sound_enabled', True) else 0)
        cv.addSubview_(self._sound_sw)

        row2_y = T(278, 22)
        cv.addSubview_(_label('Launch at login', M, row2_y+2, 300, 18))
        self._login_sw = NSSwitch.alloc().initWithFrame_(
            NSMakeRect(sw_x, row2_y, SW_W, 22))
        self._login_sw.setState_(1 if launch_agent.is_enabled() else 0)
        cv.addSubview_(self._login_sw)

        cv.addSubview_(_sep(M, T(312, 1), WIN_W-M*2))

        # ── Advanced ──────────────────────────────────────────────────────────
        cv.addSubview_(_label('Advanced', M, T(330, 18), 200, 18, bold=True))

        adv_y    = T(362, 22)
        interval = self._cfg.get('idle_refresh_minutes', 20)

        cv.addSubview_(_label('Refresh IMAP connection every',
                               M, adv_y+2, 240, 18))

        self._interval_lbl = NSTextField.alloc().initWithFrame_(
            NSMakeRect(WIN_W-M-94, adv_y, 46, 22))
        self._interval_lbl.setStringValue_(str(interval))
        self._interval_lbl.setBezeled_(False)
        self._interval_lbl.setDrawsBackground_(False)
        self._interval_lbl.setEditable_(False)
        self._interval_lbl.setSelectable_(False)
        self._interval_lbl.setAlignment_(NSTextAlignmentRight)
        self._interval_lbl.setFont_(NSFont.systemFontOfSize_(13))
        cv.addSubview_(self._interval_lbl)

        cv.addSubview_(_label('min', WIN_W-M-46, adv_y+2, 24, 18, secondary=True))

        self._stepper = NSStepper.alloc().initWithFrame_(
            NSMakeRect(WIN_W-M-22, adv_y, 22, 22))
        self._stepper.setMinValue_(5)
        self._stepper.setMaxValue_(60)
        self._stepper.setIncrement_(5)
        self._stepper.setIntValue_(interval)
        self._stepper.setValueWraps_(False)
        self._stepper.setTarget_(self)
        self._stepper.setAction_('stepInterval:')
        cv.addSubview_(self._stepper)

        cv.addSubview_(_label(
            'How often the IMAP IDLE connection is refreshed (5–60 min).',
            M, T(394, 16), WIN_W-M*2, 16, size=11, secondary=True))

        cv.addSubview_(_sep(M, T(422, 1), WIN_W-M*2))

        # ── Footer ────────────────────────────────────────────────────────────
        cancel = _btn('Cancel', WIN_W-M-148, T(450, 28))
        cancel.setTarget_(self)
        cancel.setAction_('cancelSettings:')
        cv.addSubview_(cancel)

        save = _btn('Save', WIN_W-M-70, T(450, 28), default=True)
        save.setTarget_(self)
        save.setAction_('saveSettings:')
        cv.addSubview_(save)

    # ── Button actions ─────────────────────────────────────────────────────────

    def addAccount_(self, sender):
        # Keep a strong reference so ARC doesn't deallocate the controller
        self._add_ctrl = AddAccountController.alloc().initWithParent_(self)
        if self._add_ctrl.run():
            email = str(self._add_ctrl.email_field.stringValue()).strip()
            pw    = str(self._add_ctrl.pw_field.stringValue()).strip()
            if email and pw and '@' in email:
                if not any(a['email'] == email for a in self._accounts):
                    self._accounts.append({'email': email, 'app_password': pw})
                    self._ds.rows = self._accounts
                    self._table.reloadData()

    def removeAccount_(self, sender):
        row = self._table.selectedRow()
        if row >= 0:
            self._accounts.pop(row)
            self._ds.rows = self._accounts
            self._table.reloadData()

    def stepInterval_(self, sender):
        self._interval_lbl.setStringValue_(str(int(self._stepper.intValue())))

    def saveSettings_(self, sender):
        if not self._accounts:
            a = NSAlert.alloc().init()
            a.setMessageText_('No accounts configured')
            a.setInformativeText_('Add at least one Gmail account to continue.')
            a.addButtonWithTitle_('OK')
            a.runModal()
            return

        self._cfg['accounts']             = self._accounts
        self._cfg['sound_enabled']        = bool(self._sound_sw.state())
        self._cfg['launch_at_login']      = bool(self._login_sw.state())
        try:
            self._cfg['idle_refresh_minutes'] = max(5, min(60,
                int(self._interval_lbl.stringValue())))
        except (ValueError, TypeError):
            self._cfg['idle_refresh_minutes'] = 20

        cfg_module.save(self._cfg)

        if self._login_sw.state() and not launch_agent.is_enabled():
            launch_agent.enable()
        elif not self._login_sw.state() and launch_agent.is_enabled():
            launch_agent.disable()

        self._win.close()
        NSApp.stop_(None)

    def cancelSettings_(self, sender):
        self._win.close()
        NSApp.stop_(None)

    def windowWillClose_(self, notification):
        NSApp.stop_(None)

    def show(self):
        self._win.makeKeyAndOrderFront_(None)
        NSApp.activateIgnoringOtherApps_(True)


# ── Entry point ────────────────────────────────────────────────────────────────

def main():
    app = NSApplication.sharedApplication()
    app.setActivationPolicy_(NSApplicationActivationPolicyRegular)

    # Must install Edit menu BEFORE any windows open, or Cmd+V won't work
    _install_edit_menu()

    ctrl = SettingsController.alloc().init()
    ctrl.show()
    app.run()


if __name__ == '__main__':
    main()
