from typing import Any, Generator
from ..key_handler import KeyHandler
from ..elements import Element
from ..subdialogs import AppletDialog
from ..framework import beep
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import time
from bs4 import BeautifulSoup
import requests  # type: ignore[import-untyped]
from datetime import datetime
from urllib.parse import urljoin
import json
import re
from dateutil import parser as dateparser


def extract_text(runner: Any, response_text: str) -> dict[str, str]:
    soup = BeautifulSoup(response_text, "html.parser")
    # Find the <script> tag that includes window._preloads = JSON.parse("...")
    target_script = None
    for script in soup.find_all("script"):
        if script.string and "window._preloads" in script.string:  # type: ignore[union-attr]
            target_script = script.string  # type: ignore[union-attr]
            break
    if not target_script:
        raise ValueError("No <script> block with window._preloads found.")
    # Extract the escaped JSON string inside JSON.parse("...")
    match = re.search(r'JSON\.parse\("(.*)"\)', target_script, re.DOTALL)
    if not match:
        raise ValueError("Could not find JSON.parse(...) content.")
    escaped_json = match.group(1)
    unescaped_json = bytes(escaped_json, "utf-8").decode("unicode_escape")
    # Parse the JSON blob
    data = json.loads(unescaped_json)
    post = data.get("post", {})
    # Extract fields
    title = post.get("title", "").strip()
    subtitle = post.get("subtitle", "").strip()
    author = post.get("author", {}).get("name", "").strip()
    raw_date = post.get("post_date", "").strip()
    body_html = post.get("body_html", "")
    # Parse and clean the published date
    try:
        published_dt = dateparser.parse(raw_date)
        published_date = published_dt.date().isoformat()  # YYYY-MM-DD
    except Exception:
        published_date = raw_date  # Fallback
    # Parse and clean the body HTML
    body_soup = BeautifulSoup(body_html, "html.parser")
    body_text = body_soup.get_text(separator="\n", strip=True)
    return {
        "title": title,
        "subtitle": subtitle,
        "author": author,
        "date": published_date,
        "content": body_text,
    }


def load_full_archive(url: str) -> str:
    options = Options()
    options.add_argument("--headless")
    options.add_argument("--disable-gpu")
    driver = webdriver.Chrome(options=options)
    driver.get(url)
    SCROLL_PAUSE_TIME = 1
    last_height = driver.execute_script("return document.body.scrollHeight")
    while True:
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(SCROLL_PAUSE_TIME)
        beep()
        new_height = driver.execute_script("return document.body.scrollHeight")
        if True: #new_height == last_height:
            break
        last_height = new_height
    html = driver.page_source
    driver.quit()
    return html


def extract_post_links(archive_html: str, base_url: str) -> list[str]:
    soup = BeautifulSoup(archive_html, 'html.parser')
    links = []
    for a in soup.find_all('a', href=True):
        href = a['href']  # type: ignore[index]
        if '/p/' in href:
            full_url = urljoin(base_url, href)
            links.append(full_url)
    return sorted(set(links))  # remove duplicates and sort chronologically


def parse_post(runner: Any, url: str) -> dict[str, str]:
    response = requests.get(url)
    response.raise_for_status()
    return extract_text(runner,response.text)
    #delete rest of function from here once tested
    soup = BeautifulSoup(response.text, 'html.parser')
    title_tag = soup.find('h1')
    title = title_tag.get_text(strip=True) if title_tag else 'No Title'
    subtitle_tag = soup.find('h2')
    subtitle = subtitle_tag.get_text(strip=True) if subtitle_tag else ''
    date_tag = soup.find('time')
    if date_tag and date_tag.has_attr('datetime'):
        date_str = date_tag['datetime']
        try:
            date = datetime.fromisoformat(date_str).strftime('%B %d, %Y')
        except ValueError:
            date = date_str
    else:
        date = 'Unknown Date'
    #content_div = soup.find('div', {'class': lambda x: x and 'post-body' in x})
    for div in soup.find_all('div'):
        class_attr = div.get('class')  # This returns a list or None
        print(f"Class: {class_attr}")
    content_div = soup.find('div', class_=lambda c: c and any('post-body' in cls for cls in (c if isinstance(c, list) else [c])))
    #break after parsing to see what we've got 
    runner.breakpoint()
    return {
        'title': title,
        'subtitle': subtitle,
        'date': date,
        'content': ''  # Dead code - content_html never defined
    }


def compile_posts_to_html(runner: Any, posts: list[dict[str, str]]) -> str:
    html_parts = ['<!DOCTYPE html>', '<html lang="en">', '<head>',
                  '<meta charset="UTF-8">', '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
                  '<title>The Ramekin Of Porridge - Compiled Posts</title>',
                  '</head>', '<body>']
    for post in posts:
        html_parts.append('<article>')
        html_parts.append(f'<h1>{post["title"]}</h1>')
        if post["subtitle"]:
            html_parts.append(f'<h2>{post["subtitle"]}</h2>')
        html_parts.append(f'<p><em>{post["date"]}</em></p>')
        html_parts.append(post["content"])
        html_parts.append('</article><hr>')
    html_parts.append('</body></html>')
    return '\n'.join(html_parts)


def get_posts(runner: Any) -> None:
    base_url = 'https://theramekinofporridge.substack.com'
    archive_url = f'{base_url}/archive'
    print("Loading full archive with Selenium...")
    archive_html = load_full_archive(archive_url)
    print("Extracting post links...")
    post_urls = extract_post_links(archive_html, base_url)
    print(f"Found {len(post_urls)} posts.")
    posts = []
    for idx, url in enumerate(post_urls, 1):
        #print(f"Processing post {idx}/{len(post_urls)}: {url}")
        beep()
        try:
            post_data = parse_post(runner,url)
            posts.append(post_data)
            time.sleep(1)
        except Exception as e:
            print(f"Error processing {url}: {e}")
        #break after first post to see why not appended to posts
        runner.breakpoint()
    print("Compiling to HTML...")
    compiled_html = compile_posts_to_html(runner,posts)
    with open('compiled_posts.html', 'w', encoding='utf-8') as f:
        f.write(compiled_html)
    print("Saved to compiled_posts.html")


def emails_list() -> list[str]:
    e_list=["example@acme.com","John.Doe@z.co.uk"]
    return e_list
class RunnerDialog(AppletDialog):
    """Runner applet for testing and running various utility functions.

    This applet provides hotkeys for:
    - Deleting elements from the list
    - Saving addresses to CSV file
    - Interactive Python input evaluation
    - Web scraping blog posts from Substack
    """

    def generate_content(self, filler: Any, element_type: type[Element] = Element) -> Generator[Element, None, None]:
        yield Element(self,'placeholder','use F10 to see available commands')
        return
        for e in emails_list():
            try:
                addr= e.split('<')[1].split('>')[0]
            except:
                addr=e
            yield Element(self,'address',addr)

    @KeyHandler.hotkey("Delete")
    def command_delete(self, modified_name: str) -> None:
        self.remove(self.current())
        self.say_current()

    @KeyHandler.hotkey("Control+S")
    def command_save(self, modified_name: str) -> None:
        with open('imports.csv','w') as f:
            for e in self:
                f.write(e.value+'\n')

    @KeyHandler.hotkey("Control+N")
    def command_input(self, modified_name: str) -> None:
        x=''
        while x!='q':
            x=input('type input or q to  quit')
            try:
                print(eval(x))
            except Exception as e:
                print(e)

    @KeyHandler.hotkey("Control+R")
    def command_scrape_blog(self, modified_name: str) -> None:
        get_posts(self)