#!/usr/bin/env python3

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#***************************************************************************
#* SPDX-FileCopyrightText: 2024 S. MANKOWSKI stephane@mankowski.fr
#* SPDX-FileCopyrightText: 2024 G. DE BURE support@mankowski.fr
#* SPDX-License-Identifier: GPL-3.0-or-later
#***************************************************************************
import csv
import enum
import locale
import re
import sys
from collections.abc import Generator, Iterable
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import date, datetime
from typing import TextIO

import bs4
import requests
from requests.models import Response

BASE_URL = "https://bourse.boursobank.com/_formulaire-periode/"
LOOKUP_URL = "https://www.boursorama.com/recherche/"
SYMBOL_RE = re.compile(r"/cours/([a-zA-Z0-9]+)")
NAVIGATION_RE = re.compile(r"Page \d+")


class Interval(enum.Enum):
    daily = "1d"
    monthly = "1mo"
    weekly = "1wk"


INTERVAL_TO_PERIOD = {Interval.daily: 1, Interval.monthly: 30, Interval.weekly: 7}

# Always ask for 3Y, worst case we'll have less
DURATION = "3Y"
EXPECTED_DATE_FORMAT = "%d/%m/%Y"


@dataclass
class TickerValue:
    date: date
    price: float


def ticker_to_url(ticker: str) -> str:
    response = requests.get(
        LOOKUP_URL, params=dict(query=ticker), allow_redirects=False
    )
    if not (response.is_permanent_redirect or response.is_redirect):
        raise ValueError(
            "Ticker search did not return a valid redirect: check the symbol"
        )
    redirect = response.headers.get("Location") or ""
    match = SYMBOL_RE.match(redirect)
    if match is None:
        raise ValueError(
            "Ticker search did not return a valid redirect: check the symbol"
        )
    actual_code = match.groups()[0]
    return actual_code


def request_one_page(
    interval: Interval, symbol: str, start_date: date, page: int | None = None
) -> Response:
    params: dict[str, str | int] = {
        "symbol": symbol,
        "historic_search[start_date]": start_date.strftime(EXPECTED_DATE_FORMAT),
        "historic_search[duration]": DURATION,
        "historic_search[period]": INTERVAL_TO_PERIOD[interval],
    }
    url = BASE_URL
    if page is not None:
        url += f"page-{page}"
    response = requests.get(url, params=params)
    return response


def parse_one_page(
    soup: bs4.BeautifulSoup, from_date: date
) -> Generator[TickerValue, None, date | None]:
    base_div = soup.find("div", {"data-refreshable-id": "historical-period"})
    if base_div is None:
        raise ValueError("Page does not have the expected format")
    rows = base_div.findAll("tr", {"class": "c-table__row"})  # type: ignore[union-attr]
    date = None
    for row in rows:
        cells = row.findAll("td")
        date = datetime.strptime(cells[0].text.strip(), EXPECTED_DATE_FORMAT).date()
        price = cells[1].text.strip().replace(' ', '').replace(',', '.')
        if date < from_date:
            break
        yield TickerValue(date, price)
    return date


def download_history(
    interval: Interval, symbol: str, start_date: date
) -> Generator[TickerValue]:
    response = request_one_page(interval, symbol, start_date)
    soup = bs4.BeautifulSoup(response.text, features="lxml")
    earliest_date = yield from parse_one_page(soup, start_date)
    # Now lookup if we have any more pages
    for page_link in soup.find("div", {"class": "c-pagination"}).findAll("a"):  # type: ignore[union-attr]
        if earliest_date is None or earliest_date < start_date:
            break
        if NAVIGATION_RE.match(page_link["aria-label"]):
            page_num = int(page_link.text)
            if page_num != 1:
                response = request_one_page(interval, symbol, start_date, page=page_num)
                soup = bs4.BeautifulSoup(response.text, features="lxml")
                earliest_date = yield from parse_one_page(soup, start_date)


def dump_to_csv(symbol: str, ticker_values: Iterable[TickerValue], output: TextIO):
    writer = csv.writer(output)
    writer.writerow(("date", "price"))
    for value in ticker_values:
        writer.writerow((value.date, value.price))


if __name__ == "__main__":
    symbol = sys.argv[1]
    from_date = date.fromisoformat(sys.argv[2])
    interval = Interval(sys.argv[3])

    actual_symbol = ticker_to_url(symbol)
    results = download_history(interval, actual_symbol, from_date)
    dump_to_csv(symbol, results, sys.stdout)
