import os
import sys
import json
import re
import getpass
import requests
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
from clint.textui import progress
from urllib.parse import urljoin, unquote, urlparse, quote
from html import unescape

def usage():
    print("bridrive.py [put] [http|https]://share_url path_to_file_or_directory")

def parse_url(url):
    parsed = urlparse(url)
    return {
        "protocol": parsed.scheme,
        "host": parsed.hostname,
        "port": parsed.port or (443 if parsed.scheme == "https" else 80),
        "path": parsed.path if parsed.path.endswith("/") else parsed.path + "/"
    }

def get_session_info(url, password=""):
    s = requests.Session()
    r = s.get(url, verify=False)
    r.raise_for_status()

    if "Please input the password" in r.text:        
        csrf_token_match = re.search(r"name=['\"]csrfmiddlewaretoken['\"] value=['\"]([^'\"]+)['\"]", r.text)
        if not csrf_token_match:
            raise ValueError("CSRF token not found in page")
        csrf_token = csrf_token_match.group(1)

        token_match = re.search(r'name="token" value="([\w]+)"', r.text)
        if not token_match:
            raise ValueError("Token value not found in page")
        token_value = token_match.group(1)

        form_data = {
            'csrfmiddlewaretoken': csrf_token,
            'token': token_value,
            'password': password
        }

        headers = {
            'Referer': url
        }

        post_resp = s.post(url, data=form_data, headers=headers)
        post_resp.raise_for_status()
        
        if "Please enter a correct password" in post_resp.text:
            print("Password incorrect")
            return None
        else:
            return s
    return s

def get_upload_link(session, url):
    r = session.get(url, verify=False)
    match = re.search(r"url: '(.*?)'", r.text)
    if not match:
        raise AttributeError("Upload URL not found.")
        # sys.exit(1)

    upload_url = match.group(1)

    # Jika upload_url masih relative (tidak ada http/https)
    if upload_url.startswith("/"):
        parsed_base = re.match(r"(https?://[^/]+)", url)
        if parsed_base:
            base_url = parsed_base.group(1)
            upload_url = base_url + upload_url
        else:
            raise AttributeError("Base URL could not be determined.")
            #sys.exit(1)

    resp = session.get(upload_url, verify=False, headers={
        "Accept": "application/json",
        "X-Requested-With": "XMLHttpRequest"
    })

    return json.loads(resp.text)["url"], session

def get_parent_dir(html):
    match = re.search(r"'parent_dir': \"(.*?)\"", html)
    return match.group(1) if match else "/"

def create_callback(encoder_len):
    bar = progress.Bar(expected_size=(encoder_len // 1024) + 1)
    def callback(monitor):
        if monitor == -1:
            bar.done()
        else:
            bar.show(monitor.bytes_read // 1024 + 1)
    return callback

def upload_file(filepath, upload_url, parent_dir, session):
    filename = os.path.basename(filepath)
    # filename = quote(os.path.basename(filepath))
    e = MultipartEncoder(
        fields={
            'filename': filename,
            'file': (filename, open(filepath, 'rb'), 'text/plain'),
            'parent_dir': parent_dir,
            'relative_path': ''
        }
    )
    callback = create_callback(e.len)
    monitor = MultipartEncoderMonitor(e, callback)
    headers = {
        "Content-Type": monitor.content_type
    }
    print(f"Uploading: {filename}")
    resp = session.post(upload_url, data=monitor, headers=headers)
    callback(-1)
    
    if '<p class="error ellipsis">Out of quota.</p>' in resp.text:
        print("Error: Out of quota detected during upload.")
        return False
      
    return True
  
#def submit_password(url, password):
#    session = requests.Session()
#
#    resp = session.get(url)
#    resp.raise_for_status()
#    html = resp.text
#
#    csrf_token_match = re.search(r"name=['\"]csrfmiddlewaretoken['\"] value=['\"]([^'\"]+)['\"]", html)
#    if not csrf_token_match:
#        raise ValueError("CSRF token not found in page")
#    csrf_token = csrf_token_match.group(1)
#
#    token_match = re.search(r'name="token" value="([\w]+)"', html)
#    if not token_match:
#        raise ValueError("Token value not found in page")
#    token_value = token_match.group(1)
#
#    form_data = {
#        'csrfmiddlewaretoken': csrf_token,
#        'token': token_value,
#        'password': password
#    }
#
#    headers = {
#        'Referer': url
#    }
#
#    post_resp = session.post(url, data=form_data, headers=headers)
#    post_resp.raise_for_status()
#
#    if "Please enter a correct password" in post_resp.text:
#        print("Password incorrect")
#        return None
#    else:
#        return session

def get_filename_from_html(html):
    match = re.search(r'<h2[^>]*class="file-view-hd ellipsis no-bold"[^>]* title="([^"]+)"', html)
    if match:
        return match.group(1)
    match = re.search(r'<h2[^>]*class="file-view-hd ellipsis no-bold"[^>]*>([^<]+)</h2>', html)
    if match:
        return match.group(1).strip()
    return None

def download_file(session, download_url, download_path):
    response = session.get(download_url, verify=False, stream=True)
    content_type = response.headers.get("Content-Type", "")
    if response.status_code != 200:
        raise Exception(f"Failed to download file: Status {response.status_code}")
    
    if "text/html" in content_type:
      raise ValueError("Server returned HTML instead of CSV. Check authentication or URL.")

    with open(download_path, 'wb') as f:
        for chunk in response.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)

    print(f"File downloaded and saved as: {download_path}")

def download_from_bridrive(url, pwd="", file_name = ""):
    """
    Download a file from BriDrive by submitting the password and handling the session.

    Parameters:
    -----------
    url : str
        The BriDrive URL of the shared file/folder page.
    password : str
        The password required to access the shared resource.
    file_name : str
        File name for specific file in bridrive.
    Example:
    --------
    >>> download_bridrive("http://.....", pwd="abc")
    >>> download_bridrive("http://.....", pwd="abc", file_name="data.csv")
    >>> download_bridrive("http://.....", file_name="data.csv")
    """
    download_url = url.split('?')[0]
    session = get_session_info(download_url, pwd)
    if session:
        page_resp = session.get(download_url, verify=False)
        page_resp.raise_for_status()
        html = page_resp.text
        if file_name == "":
          file_name = get_filename_from_html(html)
          if not file_name:
              raise ValueError("File not exists in bridrive")
        else:
          encoded_name = quote(file_name)
          pattern = fr'href="([^"]*{encoded_name}[^"]*)"'
          match = re.search(pattern, html)
          if not match:
            raise Exception("File not exists in bridrive")
          relative_url = unescape(match.group(1))
          download_url = urljoin(url, relative_url)
          #print("Download URL:", download_url)
        
        save_dir = os.environ.get('PATH_DATA', '.')  # fallback to current directory
        os.makedirs(save_dir, exist_ok=True)
        full_path = os.path.join(save_dir, file_name)
        if os.path.exists(full_path):
            print(f"Deleting existing file {full_path}")
            os.remove(full_path)
            if os.path.exists(full_path):
                raise RuntimeError(f"Failed to delete existing file {full_path}")

        download_url = download_url + '&dl=1'
        download_file(session, download_url, full_path)

      
def upload_to_bridrive(upload_link, path_file, delete=False):
    """
    Upload file(s) to BriDrive using internal function logic (no subprocess).

    Parameters:
    -----------
    upload_link : str
        The destination upload link for BriDrive.
    path_file : str or list of str
        Path(s) to the file(s) to upload.
    delete : bool
        If True, delete the file(s) after upload. Default is False.
        
    Example:
    --------
    >>> upload_to_bridrive("http://.....", ["file1.csv","file2.csv"], delete=True)
    >>> upload_to_bridrive("http://.....", "file.csv")
    """
    if isinstance(path_file, str):
        paths = [path_file]
    elif isinstance(path_file, list):
        paths = path_file
    else:
        print("Invalid path_file type. Must be str or list of str.")
        return

    for path in paths:
        if not os.path.exists(path):
            print(f"File does not exist: {path}")
            return
        if not os.path.isfile(path):
            print(f"Skipping non-file path: {path}")
            continue
    try:
        info = parse_url(upload_link)
        session = get_session_info(upload_link)
        r = session.get(upload_link)
        html = r.text
        parent_dir = get_parent_dir(html)
        upload_url, session = get_upload_link(session, upload_link)

        for path in paths:
            success  = upload_file(path, upload_url, parent_dir, session)
            if not success:
                print(f"Upload failed for {path} due to quota limit.")
                break
            print(f"Uploaded: {path}")

        if delete:
            for f in paths:
                try:
                    os.remove(f)
                    print(f"Deleted file: {f}")
                except Exception as e:
                    print(f"Could not delete file {f}: {e}")

    except Exception as e:
        print(f"Error during upload: {e}")
        return