import json
import os
import re
import socket
import subprocess
import urllib.parse
import urllib.request
import urllib.error
from dataclasses import dataclass
from typing import Any, BinaryIO, Optional


class API:
    def __init__(self, server_root_path: str = "."):
        self.server_root_path = os.path.abspath(server_root_path)
        self.uploaded_files: set[str] = set()

    def list_directory(self, path):
        try:
            raw_files = os.listdir(path)
        except OSError:
            return None
        
        raw_files.sort(key=lambda a: a.lower())
        files = []
        for filename in raw_files:
            filepath = os.path.join(path, filename)
            
            try:
                is_link = os.path.islink(filepath)
                
                if is_link:
                    try:
                        os.stat(filepath)
                        is_dir = os.path.isdir(filepath)
                    except (OSError, FileNotFoundError):
                        is_dir = False
                else:
                    is_dir = os.path.isdir(filepath)
                
                if is_dir:
                    size = 0
                else:
                    try:
                        size = os.path.getsize(filepath)
                    except (OSError, FileNotFoundError):
                        size = 0
                
                files.append({
                    'name': filename,
                    'is_dir': is_dir,
                    'size': size,
                    'is_recent': filename in self.uploaded_files,
                })
            except (OSError, FileNotFoundError) as e:
                print(f"Warning: Could not process file '{filename}': {e}")
                continue
        
        return files

    def list_directory_for_ui(self, path: str):
        items = self.list_directory(path)
        if items is None:
            return None

        files_data = []
        for item in items:
            name = item["name"]
            is_dir = bool(item["is_dir"])
            if is_dir:
                size_display = "-"
            else:
                size_display = f"{item['size'] / 1024:.1f} KB" if item['size'] > 0 else "0 KB"
            files_data.append((name, size_display, is_dir, bool(item["is_recent"])))
        return files_data

    def get_display_path(self, path: str) -> str:
        root_path = os.path.abspath(self.server_root_path)
        current_path = os.path.abspath(path)
        if not current_path.startswith(root_path):
            return current_path.replace("\\", "/")

        rel_path = os.path.relpath(current_path, root_path)
        if rel_path in (".", ""):
            display = root_path
        else:
            display = os.path.join(root_path, rel_path)
        return display.replace("\\", "/")

    def save_upload_multipart(self, fileobj: BinaryIO, filename: str, dest_dir: str = ".") -> str:
        safe_name = os.path.basename(filename or "uploaded_file")
        filepath = os.path.join(dest_dir, safe_name)
        with open(filepath, "wb") as f:
            f.write(fileobj.read())
        self.uploaded_files.add(safe_name)
        return safe_name

    def save_upload_raw(self, data: bytes, filename: str, dest_dir: str = ".") -> str:
        safe_name = os.path.basename(filename or "uploaded_file")
        filepath = os.path.join(dest_dir, safe_name)
        with open(filepath, "wb") as f:
            f.write(data)
        self.uploaded_files.add(safe_name)
        return safe_name

    def parse_upload_filename_from_url(self, url_path: str, default: str = "uploaded_file") -> str:
        parsed = urllib.parse.urlparse(url_path)
        query = urllib.parse.parse_qs(parsed.query)
        return query.get("filename", [default])[0]


    def get_local_ip(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
            s.close()
            return ip
        except Exception:
            return "127.0.0.1"
        
    def get_all_interfaces(self):
        interfaces = []
        
        try:
            result = subprocess.run(['ip', 'addr'], capture_output=True, text=True, timeout=2)
            if result.returncode == 0:
                current_iface = None
                for line in result.stdout.split('\n'):
                    iface_match = re.match(r'^\d+: ([^:]+):', line)
                    if iface_match:
                        current_iface = iface_match.group(1).split('@')[0]
                    
                    ip_match = re.search(r'inet (\d+\.\d+\.\d+\.\d+)', line)
                    if ip_match and current_iface:
                        ip = ip_match.group(1)
                        if ip != '127.0.0.1' and not ip.startswith('127.'):
                            interfaces.append({
                                'name': current_iface,
                                'ip': ip
                            })
        except Exception as e:
            print(f"[!] Error while fetching interfaces: {e}")
        if not interfaces:
            default_ip = self.get_local_ip()
            interfaces.append({
                'name': 'default',
                'ip': default_ip
            })
        interfaces.append({
            'name': 'localhost',
            'ip': '127.0.0.1'
        })
        return interfaces

    def json_bytes(self, payload: Any) -> bytes:
        return json.dumps(payload).encode("utf-8")

    PROXY_CHUNK_SIZE = 64 * 1024  # 64 KB per chunk

    def proxy_stream(self, url: str, chunk_size: int = None):
        if chunk_size is None:
            chunk_size = self.PROXY_CHUNK_SIZE

        headers = {
            'User-Agent': (
                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
                '(KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
            )
        }

        try:
            req = urllib.request.Request(url, headers=headers)
            response = urllib.request.urlopen(req, timeout=10)
        except urllib.error.HTTPError as e:
            yield (None, None, None, f"HTTP Error {e.code}: {e.reason}")
            return
        except urllib.error.URLError as e:
            yield (None, None, None, f"URL Error: {e.reason}")
            return
        except Exception as e:
            yield (None, None, None, f"Error: {str(e)}")
            return

        content_type = response.getheader('Content-Type') or 'application/octet-stream'
        content_length = response.getheader('Content-Length')

        yield (None, content_type, content_length, None)

        try:
            while True:
                chunk = response.read(chunk_size)
                if not chunk:
                    break
                yield chunk
        finally:
            response.close()
    

if __name__ == "__main__":
    api = API()
    print("Local IP:", api.get_local_ip())
    print("All Interfaces:", api.get_all_interfaces())
    print("Directory Listing:", api.list_directory("."))