#!/usr/bin/python
# -*- coding: utf-8 -*-

# --------------------------------------------------------------------------- #
#                                                                             #
#    Plugin for iSida Jabber Bot                                              #
#    Weather with wttr.in  (lang=ru, format=3 / &3d)                          #
#                                                                             #
#    This program is free software: you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation, either version 3 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
#                                                                             #
# --------------------------------------------------------------------------- #

import re
import os
import time
import urllib.request
import urllib.parse

DEBUG_FILE = data_folder % 'wttr_debug.log'
DEFAULT_CITY = 'Engels'
_HTTP_TIMEOUT = 15

# --------------------------------------------------------------------------- #
#    Helpers: debug log + Cyrillic->Latin fallback                              #
# --------------------------------------------------------------------------- #

def _debug(msg):
    try:
        with open(DEBUG_FILE, 'a', encoding='utf-8') as f:
            f.write('[%s] %s\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), msg))
    except Exception:
        pass

RUS_TO_LAT = {
    'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
    'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm',
    'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
    'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'shch',
    'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya',
    'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo',
    'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M',
    'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
    'Ф': 'F', 'Х': 'Kh', 'Ц': 'Ts', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Shch',
    'Ъ': '', 'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya'
}

def transliterate(text):
    return ''.join(RUS_TO_LAT.get(c, c) for c in text)

# --------------------------------------------------------------------------- #
#    HTTP fetch                                                                  #
#    NB: the helper is intentionally named _wttr_fetch_url (NOT _fetch_url)   #
#    because iSida loads every plugin into one shared global namespace and     #
#    www.py / rss.py already define _fetch_url(url, limit=None) returning a    #
#    (content, headers, status) tuple. A name clash there is what made the    #
#    weather command crash with "'tuple' object has no attribute 'lower'".     #
# --------------------------------------------------------------------------- #

_ANSI_RE = re.compile(r'\x1b\[[0-9;]*m')

def _strip_ansi(text):
    return _ANSI_RE.sub('', text)

def _is_error(data):
    if not data or not isinstance(data, str):
        return True
    low = data[:64].lower()
    return ('location not found' in low
            or data.startswith('ERROR')
            or data.startswith('<!DOCTYPE')
            or data.startswith('<html'))

def _wttr_fetch_url(url):
    _debug('Fetching URL: %s' % url)
    try:
        req = urllib.request.Request(
            url,
            headers={
                'User-Agent': 'curl/8.4.0',
                'Accept': 'text/plain'
            }
        )
        with urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT) as resp:
            raw = resp.read()
            if isinstance(raw, str):
                data = raw
            elif isinstance(raw, (bytes, bytearray)):
                try:
                    data = bytes(raw).decode('utf-8')
                except UnicodeDecodeError:
                    data = bytes(raw).decode('latin-1')
            else:
                data = str(raw)
            _debug('Response length: %d' % len(data))
            return _strip_ansi(data).strip()
    except Exception as e:
        _debug('Fetch error: %s' % str(e))
        return None

def _build_report_url(city):
    return 'https://wttr.in/%s?lang=ru&3d' % urllib.parse.quote(city)

# --------------------------------------------------------------------------- #
#    Field extractors                                                           #
# --------------------------------------------------------------------------- #

# Full Unicode letter range (Cyrillic U+0400-U+04FF plus ASCII letters) so that
# conditions returned in English (wttr sometimes ignores lang=ru) are kept too.
# Never use a short Cyrillic range like "а-и" — it drops letters and yields
# fragments like "не"/"Пе".
_COND_RE = re.compile(r'[\u0400-\u04FFA-Za-z][\u0400-\u04FFA-Za-z…\s]+')
_TEMP_RE = re.compile(r'([+\-]?\d+(?:\([\+\-]?\d+\))?)\s*°C')
_WIND_RE = re.compile(r'([\u2190-\u2199])?\s*(\d+(?:-\d+)?)\s*км/ч')
_PRECIP_RE = re.compile(r'(\d+\.?\d*)\s*мм')

_MONTH_GEN = {
    'янв.': 'января', 'фев.': 'февраля', 'мар.': 'марта', 'апр.': 'апреля',
    'май.': 'мая', 'июн.': 'июня', 'июл.': 'июля', 'авг.': 'августа',
    'сен.': 'сентября', 'окт.': 'октября', 'ноя.': 'ноября', 'дек.': 'декабря'
}

_DIR_NAMES = {
    '→': 'Западный',  '←': 'Восточный', '↑': 'Южный',  '↓': 'Северный',
    '↗': 'Юго-Восточный', '↖': 'Северо-Восточный',
    '↘': 'Юго-Западный',  '↙': 'Северо-Западный'
}

PERIODS = ('Утро', 'День', 'Вечер', 'Ночь')

def _main_temp(cell):
    m = _TEMP_RE.search(cell)
    if not m:
        return ''
    t = m.group(1).split('(')[0]
    if t and t[0].isdigit():
        t = '+' + t
    return t

def _kmh_to_ms(speed):
    return '-'.join(str(int(round(int(p) / 3.6))) for p in speed.split('-'))

def _wind_parts(cell):
    """Return 'ветер <Dir> <speed> м/с' for a wind cell, or ''."""
    m = _WIND_RE.search(cell)
    if not m:
        return ''
    direction = _DIR_NAMES.get(m.group(1) or '', '')
    speed = _kmh_to_ms(m.group(2))
    if direction:
        return 'ветер %s %s м/с' % (direction, speed)
    return 'ветер %s м/с' % speed

def _precip(cell):
    m = _PRECIP_RE.search(cell)
    if not m:
        return ''
    val = m.group(0).strip()
    # only show real (non-zero) precipitation
    try:
        if float(m.group(1)) == 0:
            return ''
    except (ValueError, IndexError):
        pass
    return val

def _condition(cell):
    # keep Cyrillic + Latin + ellipsis + spaces, then grab the trailing phrase
    txt = re.sub(r'[^\u0400-\u04FFA-Za-z…\s]', ' ', cell)
    m = _COND_RE.search(txt)
    return m.group(0).strip() if m else ''

def _split_cells(row):
    parts = row.split('│')
    return [c.strip() for c in parts[1:-1]] if len(parts) > 1 else [c.strip() for c in parts]

# --------------------------------------------------------------------------- #
#    URLs + fetch                                                              #
# --------------------------------------------------------------------------- #

def _build_current_url(city):
    return 'https://wttr.in/%s?lang=ru&format=3' % urllib.parse.quote(city)

def _build_report_url(city):
    return 'https://wttr.in/%s?lang=ru&3d' % urllib.parse.quote(city)

def _wttr_report(city):
    data = _wttr_fetch_url(_build_report_url(city))
    if _is_error(data):
        fallback = _wttr_fetch_url(_build_report_url(transliterate(city)))
        if not _is_error(fallback):
            data = fallback
        else:
            return None
    if not data or _is_error(data):
        _debug('No report returned for city: %s' % city)
        return None
    return data

# --------------------------------------------------------------------------- #
#    URLs + fetch + small helpers                                               #
# --------------------------------------------------------------------------- #

_MONTH_GEN_BY_NUM = [
    None, 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля',
    'августа', 'сентября', 'октября', 'ноября', 'декабря'
]

def _build_report_url(city):
    return 'https://wttr.in/%s?lang=ru&3d' % urllib.parse.quote(city)

def _wttr_report(city):
    data = _wttr_fetch_url(_build_report_url(city))
    if _is_error(data):
        fallback = _wttr_fetch_url(_build_report_url(transliterate(city)))
        if not _is_error(fallback):
            data = fallback
        else:
            return None
    if not data or _is_error(data):
        _debug('No report returned for city: %s' % city)
        return None
    return data

def _today_russian():
    now = time.localtime()
    return '%d %s' % (now.tm_mday, _MONTH_GEN_BY_NUM[now.tm_mon])

def _location_city(report):
    """Russian city name from the 'Местоположение: Энгельс, ...' tail line."""
    m = re.search(r'Местоположение:\s*(.+)', report)
    if m:
        first = m.group(1).split(',')[0].strip()
        if first:
            return first
    return None

# --------------------------------------------------------------------------- #
#    Current weather (plain text, same parsing as forecast, NO emoji)            #
# --------------------------------------------------------------------------- #

def get_current(city):
    report = _wttr_report(city)
    if not report:
        return None
    lines = report.split('\n')
    tb = next((i for i, l in enumerate(lines) if '┌' in l and '┐' in l), len(lines))
    head = '\n'.join(lines[:tb])
    # condition is the figure line that has letters but no temp/wind/vis/precip
    # units (°C / км / мм). Skip the "Прогноз погоды" header and the unit rows.
    sym_line = ''
    for l in lines[:tb]:
        if 'Прогноз' in l or '°C' in l or 'мм' in l or 'км' in l:
            continue
        if re.search(r'[A-Za-z\u0400-\u04FF]', l):
            sym_line = l
            break
    cond = _condition(sym_line)
    temp = _main_temp(head)
    wind = _wind_parts(head)
    label = _location_city(report) or city
    out = [
        '%s, %s' % (label, _today_russian()),
        'Погода: %s' % (cond if cond else 'н/д'),
        'Температура: %s°C' % temp if temp else 'Температура: н/д',
        'Ветер: %s' % wind if wind else 'Ветер: —'
    ]
    return '\n'.join(out)

# --------------------------------------------------------------------------- #
#    3-day forecast (parse &3d tables -> clean structured text)                #
# --------------------------------------------------------------------------- #

def _period_line(period, cond, temp, wind, precip):
    bits = []
    if cond:
        bits.append(cond)
    if temp:
        bits.append(temp)
    if wind:
        bits.append(wind)
    if precip:
        bits.append(precip)
    return '%s: %s' % (period, ', '.join(bits) if bits else 'н/д')

def _parse_day(date_str, rows):
    matrix = [_split_cells(r) for r in rows if '│' in r]
    if not matrix:
        return None
    out = ['Дата: %s' % date_str]
    for p in range(4):
        cell_cond = matrix[1][p] if len(matrix) > 1 and p < len(matrix[1]) else ''
        cell_temp, cell_wind, cell_precip = '', '', ''
        for ri in range(2, len(matrix)):
            if p >= len(matrix[ri]):
                continue
            if not cell_temp:
                t = _main_temp(matrix[ri][p])
                if t:
                    cell_temp = t
            if not cell_wind:
                w = _WIND_RE.search(matrix[ri][p])
                if w:
                    cell_wind = _wind_parts(matrix[ri][p])
            if not cell_precip:
                pr = _precip(matrix[ri][p])
                if pr:
                    cell_precip = pr
        out.append(_period_line(
            PERIODS[p],
            _condition(cell_cond),
            (cell_temp + '°C') if cell_temp else '',
            cell_wind,
            cell_precip))
    return '\n'.join(out)

def get_forecast(city):
    report = _wttr_report(city)
    if not report:
        return []
    return _parse_forecast(report)

def _parse_forecast(report):
    lines = report.split('\n')
    days = []
    n = len(lines)
    i = 0
    while i < n:
        line = lines[i]
        m = re.search(r'(Пн|Вт|Ср|Чт|Пт|Сб|Вс)\.\s*(\d{1,2})\s+([а-яё]+)\.', line)
        if m and '┤' in line:
            mon = _MONTH_GEN.get(m.group(3) + '.', m.group(3))
            date = '%s %s' % (m.group(2), mon)
            rows = []
            j = i + 1
            while j < n:
                lj = lines[j]
                if '│' in lj:
                    rows.append(lj)
                elif '└' in lj and '┘' in lj and '─' in lj:
                    break
                j += 1
            day = _parse_day(date, rows)
            if day:
                days.append(day)
            i = j
        else:
            i += 1
    return days

# --------------------------------------------------------------------------- #
#    Commands                                                                   #
# --------------------------------------------------------------------------- #

def wttr(type, jid, nick, text):
    _debug('=== wttr command ===')
    _debug('Raw text: "%s"' % text)
    try:
        text = (text or '').strip()
        parts = text.split()
        is_forecast = False
        city = DEFAULT_CITY
        if parts:
            if parts[0].lower() == 'fc':
                is_forecast = True
                city = ' '.join(parts[1:])
            else:
                city = text
        city = city.strip() or DEFAULT_CITY
        _debug('City: %s, forecast: %s' % (city, is_forecast))

        if is_forecast:
            days = get_forecast(city)
            if not days:
                send_msg(type, jid, nick, L('Не могу найти погоду для: %s' % city, '%s/%s' % (jid, nick)))
                return
            result = '\n\n'.join(days)
        else:
            result = get_current(city)
            if not result:
                send_msg(type, jid, nick, L('Не могу найти погоду для: %s' % city, '%s/%s' % (jid, nick)))
                return

        send_msg(type, jid, nick, L(result, '%s/%s' % (jid, nick)))
        _debug('=== wttr command finished ===')
    except Exception as e:
        _debug('EXCEPTION: %s' % str(e))
        send_msg(type, jid, nick, L('Ошибка: %s' % str(e), '%s/%s' % (jid, nick)))

def wttr_debug(type, jid, nick, text):
    try:
        if os.path.exists(DEBUG_FILE):
            with open(DEBUG_FILE, 'r', encoding='utf-8') as f:
                tail = f.readlines()[-30:]
                send_msg(type, jid, nick, L('Лог (последние 30 строк):\n%s' % ''.join(tail), '%s/%s' % (jid, nick)))
        else:
            send_msg(type, jid, nick, L('Лог-файл не существует', '%s/%s' % (jid, nick)))
    except Exception as e:
        send_msg(type, jid, nick, L('Ошибка: %s' % str(e), '%s/%s' % (jid, nick)))

def wttr_help(type, jid, nick, text):
    send_msg(type, jid, nick, L("""Погода через wttr.in

Команды:
  wttr [город]      — текущая погода
  wttr fc [город]   — прогноз на 3 дня
  wttr_help         — справка
  wttr_debug        — лог отладки

Примеры:
  wttr Engels
  wttr Москва
  wttr fc Санкт-Петербург
  wttr fc   — прогноз для Энгельса""", '%s/%s' % (jid, nick)))

# --------------------------------------------------------------------------- #
#    Регистрация                                                               #
# --------------------------------------------------------------------------- #

global execute

execute = [
    (3, 'wttr', wttr, 2, 'Погода в городе (wttr.in)'),
    (3, 'wttr_help', wttr_help, 2, 'Помощь по команде wttr'),
    (9, 'wttr_debug', wttr_debug, 2, 'Показать лог отладки wttr'),
]
