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

# --------------------------------------------------------------------------- #
#                                                                             #
#    Isida-NG Jabber Bot (fork of iSida)                                      #
#    Isida-NG Copyright (C) 2026 Luciferus <luciferus@ubunix.pro>             #
#    Project home: https://git.ubunix.pro/Luciferus/Isida-NG                  #
#                                                                             #
#    Meteo plugin based on Open-Meteo API                                     #
#                                                                             #
#    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 json
import time
import random
import urllib.request
import urllib.parse

METEO_GEO_URL = 'https://geocoding-api.open-meteo.com/v1/search'
METEO_FORECAST_URL = 'https://api.open-meteo.com/v1/forecast'
METEO_UA = {'User-Agent': 'Mozilla/5.0'}

# WMO weather codes → русский текст
METEO_WMO_RU = {
    0: 'ясно',
    1: 'преимущественно ясно',
    2: 'переменная облачность',
    3: 'пасмурно',
    45: 'туман',
    48: 'туман с изморозью',
    51: 'лёгкая морось',
    53: 'умеренная морось',
    55: 'сильная морось',
    56: 'лёгкая ледяная морось',
    57: 'сильная ледяная морось',
    61: 'небольшой дождь',
    63: 'умеренный дождь',
    65: 'сильный дождь',
    66: 'лёгкий ледяной дождь',
    67: 'сильный ледяной дождь',
    71: 'небольшой снег',
    73: 'умеренный снег',
    75: 'сильный снег',
    77: 'снежная крупа',
    80: 'небольшие ливни',
    81: 'умеренные ливни',
    82: 'сильные ливни',
    85: 'небольшие снегопады',
    86: 'сильные снегопады',
    95: 'гроза',
    96: 'гроза с небольшим градом',
    99: 'гроза с сильным градом',
}

METEO_NO_CITY_PHRASES = [
    'Ась?',
    'Ничего не забыл?',
    'Телепаты в отпуске, назови город.',
    'И какой город?',
    'Город-то какой?',
]


def _meteo_http_get(url):
    try:
        req = urllib.request.Request(url, headers=METEO_UA)
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read().decode('utf-8'))
    except Exception as e:
        pprint('[METEO] HTTP error %s: %s' % (url, e), 'red')
        return None


def _meteo_wmo(code):
    return METEO_WMO_RU.get(code, 'код %s' % code)


def _meteo_geocode(name, country_code=None):
    """Возвращает dict с latitude/longitude/timezone/name/country или None."""
    params = {
        'name': name,
        'count': 1,
        'language': 'ru',
        'format': 'json',
    }
    if country_code:
        params['countryCode'] = country_code.upper()
    url = METEO_GEO_URL + '?' + urllib.parse.urlencode(params)
    data = _meteo_http_get(url)
    if not data or not data.get('results'):
        return None
    r = data['results'][0]
    return {
        'lat': r.get('latitude'),
        'lon': r.get('longitude'),
        'tz': r.get('timezone', 'auto'),
        'name': r.get('name', name),
        'country': r.get('country', ''),
        'admin': r.get('admin1', ''),
    }


def _meteo_forecast(lat, lon, days=1):
    params = {
        'latitude': lat,
        'longitude': lon,
        'current': 'temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,weather_code',
        'daily': 'temperature_2m_max,temperature_2m_min,precipitation_probability_max,sunrise,sunset',
        'timezone': 'auto',
        'forecast_days': days,
        'wind_speed_unit': 'ms',
        'temperature_unit': 'celsius',
        'precipitation_unit': 'mm',
    }
    url = METEO_FORECAST_URL + '?' + urllib.parse.urlencode(params)
    return _meteo_http_get(url)


def _meteo_pressure_mmhg(hpa):
    """гПа → мм рт. ст."""
    if hpa is None:
        return None
    return round(hpa * 0.750062, 1)


def _meteo_format_current(data):
    cur = data.get('current') or {}
    lines = []
    t = cur.get('temperature_2m')
    feels = cur.get('apparent_temperature')
    if t is not None:
        s = 'Температура: %s°C' % t
        if feels is not None and feels != t:
            s += ' (ощущается как %s°C)' % feels
        lines.append(s)
    wmo = cur.get('weather_code')
    if wmo is not None:
        lines.append('Небо: %s' % _meteo_wmo(wmo))
    hum = cur.get('relative_humidity_2m')
    if hum is not None:
        lines.append('Влажность: %s%%' % hum)
    wind = cur.get('wind_speed_10m')
    if wind is not None:
        lines.append('Ветер: %s м/с' % wind)
    return lines


def _meteo_format_daily(data, days):
    daily = data.get('daily') or {}
    dates = daily.get('time') or []
    tmax = daily.get('temperature_2m_max') or []
    tmin = daily.get('temperature_2m_min') or []
    prec = daily.get('precipitation_probability_max') or []
    rise = daily.get('sunrise') or []
    sett = daily.get('sunset') or []

    lines = []
    for i in range(min(days, len(dates))):
        date = dates[i]
        parts = [date]
        if i < len(tmax) and i < len(tmin):
            parts.append('%s..%s°C' % (tmin[i], tmax[i]))
        if i < len(prec) and prec[i] is not None:
            parts.append('осадки %s%%' % prec[i])
        if i < len(rise) and i < len(sett):
            # sunrise/sunset в формате 2026-09-16T06:12
            r = rise[i].split('T')[-1] if 'T' in rise[i] else rise[i]
            s = sett[i].split('T')[-1] if 'T' in sett[i] else sett[i]
            parts.append('☀ %s-%s' % (r, s))
        lines.append(' • ' + ' | '.join(parts))
    return lines


def _meteo_location_title(loc):
    parts = [loc['name']]
    if loc.get('admin'):
        parts.append(loc['admin'])
    if loc.get('country'):
        parts.append(loc['country'])
    return ', '.join(parts)


def _meteo_parse_args(text):
    """Разбирает аргументы: город [код страны] или координаты 'lat,lon'."""
    text = text.strip()
    if not text:
        return None, None, None

    # Координаты: "55.75,37.61" или "55.75 37.61"
    coord_match = text.replace(' ', ',')
    if ',' in coord_match:
        parts = [p.strip() for p in coord_match.split(',') if p.strip()]
        if len(parts) == 2:
            try:
                lat = float(parts[0])
                lon = float(parts[1])
                if -90 <= lat <= 90 and -180 <= lon <= 180:
                    return ('coords', lat, lon), None, None
            except ValueError:
                pass

    # Город + опциональный код страны
    parts = text.split()
    if len(parts) >= 2 and len(parts[-1]) == 2 and parts[-1].isalpha():
        country = parts[-1].upper()
        city = ' '.join(parts[:-1])
    else:
        country = None
        city = text
    return ('city', city), country, None


def meteo(type, jid, nick, text):
    room = getRoom(jid)
    args, country, _ = _meteo_parse_args(text)

    if args is None:
        msg = random.choice(METEO_NO_CITY_PHRASES)
        send_msg(type, jid, nick, msg)
        return

    if args[0] == 'coords':
        _, lat, lon = args
        loc = {'lat': lat, 'lon': lon, 'tz': 'auto', 'name': '%.4f, %.4f' % (lat, lon), 'country': '', 'admin': ''}
    else:
        _, city = args
        loc = _meteo_geocode(city, country)
        if not loc:
            send_msg(type, jid, nick, L('City not found: %s', '%s/%s' % (jid, nick)) % text)
            return

    data = _meteo_forecast(loc['lat'], loc['lon'], days=1)
    if not data:
        send_msg(type, jid, nick, L('Weather service unavailable', '%s/%s' % (jid, nick)))
        return

    title = _meteo_location_title(loc)
    lines = [title]
    lines += _meteo_format_current(data)
    lines += _meteo_format_daily(data, 1)
    send_msg(type, jid, nick, '\n'.join(lines))


def meteo_plus(type, jid, nick, text):
    """Расширенный прогноз на 3 дня — отправляется в приват MUC-юзера."""
    args, country, _ = _meteo_parse_args(text)

    if args is None:
        send_msg(type, jid, nick, random.choice(METEO_NO_CITY_PHRASES))
        return

    if args[0] == 'coords':
        _, lat, lon = args
        loc = {'lat': lat, 'lon': lon, 'tz': 'auto', 'name': '%.4f, %.4f' % (lat, lon), 'country': '', 'admin': ''}
    else:
        _, city = args
        loc = _meteo_geocode(city, country)
        if not loc:
            send_msg(type, jid, nick, L('City not found: %s', '%s/%s' % (jid, nick)) % text)
            return

    data = _meteo_forecast(loc['lat'], loc['lon'], days=3)
    if not data:
        send_msg(type, jid, nick, L('Weather service unavailable', '%s/%s' % (jid, nick)))
        return

    title = _meteo_location_title(loc)
    lines = [title, '— Сейчас —']
    lines += _meteo_format_current(data)
    lines.append('— Прогноз на 3 дня —')
    lines += _meteo_format_daily(data, 3)

    # В приват MUC-юзера
    try:
        target = '%s/%s' % (getRoom(jid), nick)
        sender(xmpp.Message(target, '\n'.join(lines), "chat"))
    except Exception as e:
        pprint('[METEO] Send error: %s' % e, 'red')


def meteo_search(type, jid, nick, text):
    """Поиск города: показать все совпадения."""
    text = text.strip()
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return

    params = {'name': text, 'count': 10, 'language': 'ru', 'format': 'json'}
    url = METEO_GEO_URL + '?' + urllib.parse.urlencode(params)
    data = _meteo_http_get(url)
    if not data or not data.get('results'):
        send_msg(type, jid, nick, L('Nothing found', '%s/%s' % (jid, nick)))
        return

    lines = [L('Found:', '%s/%s' % (jid, nick))]
    for r in data['results']:
        parts = [r.get('name', '?')]
        if r.get('admin1'):
            parts.append(r['admin1'])
        if r.get('country'):
            parts.append(r['country'])
        code = r.get('country_code', '')
        lines.append(' • %s [%s]' % (', '.join(parts), code))
    send_msg(type, jid, nick, '\n'.join(lines))


global execute

execute = [
    (3, 'meteo', meteo, 2, 'Current weather + today forecast. Usage: meteo <city> [country_code] or meteo <lat>,<lon>'),
    (3, 'meteo+', meteo_plus, 2, 'Extended 3-day forecast (sent to private). Usage: meteo+ <city> [country_code]'),
    (3, 'meteo_search', meteo_search, 2, 'Search city by name. Usage: meteo_search <query>'),
]
