import os
import platform
import sqlite3
import sys
from collections import OrderedDict
from dataclasses import dataclass
from datetime import date
from datetime import datetime
from pathlib import Path
from typing import List
from typing import Optional
from typing import Type

import appdirs
from browserexport.browsers.all import DEFAULT_BROWSERS
from browserexport.browsers.common import Browser
from browserexport.merge import merge_visits
from browserexport.merge import read_visits
from browserexport.model import Visit
from sqlite_backup import sqlite_backup

from . import __version__
from . import cleaning


def default_output_folder() -> Path:
    data_dir: str = appdirs.user_data_dir(
        appname=os.path.basename(sys.argv[0]), version=__version__
    )

    output_dir: Path = Path(data_dir) / Path(datetime.now().strftime("%Y%m%d_%H%M%S"))
    output_dir.mkdir(parents=True, exist_ok=True)
    return output_dir


def to_markdown_link(event: Visit) -> str:
    if "file://" in event.url:
        return f"*{cleaning.clean(event.url.lstrip('file://'))}*"

    if (
        event.metadata is not None
        and event.metadata.title is not None
        and len(event.metadata.title) > 0
    ):
        return f"[{cleaning.clean(event.metadata.title)}]({cleaning.clean_url(url=event.url)})"

    return f"<{event.url}>"  # default


def from_browser(browser_name: str, profile: str = "*") -> sqlite3.Connection:
    """
    Return an in-memory SQLite database, containing the history data from the given browser.
    """
    matches = [
        b for b in DEFAULT_BROWSERS if b.__name__.lower() == browser_name.lower()
    ]
    if not matches:
        raise ValueError(f"Browser {browser_name} not found")

    browser: Type[Browser] = matches[0]
    if browser.has_save:
        # as we are not providing an output destination here, the data
        # will be saved in-memory
        return sqlite_backup(source=browser.locate_database(profile))

    raise ValueError(f"Browser {browser_name} doesn't know how to save database")


def prettify(
    history_json: Optional[Path],
    dumping_folder: Optional[Path],
    browser: Optional[str],
    browser_profile: str,
) -> None:
    def empty():
        yield from ()

    visits: List[Visit] = list(
        merge_visits(
            [
                read_visits(history_json) if history_json is not None else empty(),
                read_visits(from_browser(browser, profile=browser_profile))
                if browser is not None
                else empty(),
            ]
        )
    )

    grouped: OrderedDict[date, List[Visit]] = OrderedDict()
    for visit in visits:  # type: Visit
        grouped.setdefault(visit.dt.date(), []).append(visit)

    out: Path = (
        dumping_folder if dumping_folder is not None else default_output_folder()
    )

    for key, val in grouped.items():
        page: Page = Page(dt=key, visits=val)
        page.dump(out_folder=out)

    print(f"Browser history files have been dumped to:\n{out}")


@dataclass(frozen=True)
class Page:
    dt: date
    visits: List[Visit]

    def dump(self, out_folder: Path) -> None:
        assert (
            len(self.visits) > 0
        ), f"Cannot dump browsing history page with no recorded visits ('{self.page_name}')"

        with open(os.path.join(out_folder, self.page_name + ".md"), "w") as f:
            f.write(f"# {self.page_name}")
            f.write("\n\n")
            f.write(datetime.strftime(self.visits[0].dt, "%Y%m%d%H%M%S"))
            f.write("\n\n")
            f.write(
                f"This file was autogenerated by `{platform.node()}` using `{os.path.abspath(__file__)}`."
            )
            f.write("\n\n")
            for visit in sorted(self.visits, key=lambda v: v.dt):  # type: Visit
                f.write(
                    f"- {datetime.strftime(visit.dt, '%H:%M')} {to_markdown_link(visit)}"
                )
                f.write("\n")

    @property
    def page_name(self) -> str:
        return f"{self.formatted_dt}, online browsing history"

    @property
    def formatted_dt(self) -> str:
        return (
            date.strftime(self.dt, "%B %#d, %Y")
            if platform.system() == "Windows"
            else date.strftime(self.dt, "%B %-d, %Y")
        )
