from __future__ import annotations
import typing
import csv
import json
import os
import logging
from urllib.parse import urljoin, urlparse

from ..base import BaseFileIterable, BaseCodec
from ..helpers.utils import rowincount


DEFAULT_ENCODING = 'utf8'
DEFAULT_DELIMITER = ','


class CSVWIterable(BaseFileIterable):
    """CSV on the Web (CSVW) iterable - CSV with JSON-LD metadata"""
    
    def __init__(self, filename: str = None, stream: typing.IO = None, codec: BaseCodec = None,
                 metadata_file: str = None, delimiter: str = None, quotechar: str = '"',
                 mode: str = 'r', encoding: str = None, options: dict = {}):
        logging.debug(f'Params: encoding: {encoding}, options {options}')
        self.encoding = None
        self.fileobj = stream
        
        if encoding is not None:
            self.encoding = encoding
        elif 'encoding' in options.keys() and options['encoding'] is not None:
            self.encoding = options['encoding']
        
        if mode == 'r':
            if self.encoding is None:
                self.encoding = DEFAULT_ENCODING
        elif self.encoding is None:
            self.encoding = DEFAULT_ENCODING
        
        logging.debug(f'Final encoding {self.encoding}')
        
        super(CSVWIterable, self).__init__(filename, stream, codec=codec, binary=False,
                                           encoding=self.encoding, mode=mode, options=options)
        
        if not delimiter:
            self.delimiter = DEFAULT_DELIMITER
        else:
            self.delimiter = delimiter
        self.quotechar = quotechar
        
        # Metadata handling
        self.metadata_file = metadata_file
        if 'metadata_file' in options:
            self.metadata_file = options['metadata_file']
        
        # If no metadata file specified, try to find it
        if self.metadata_file is None and filename:
            # CSVW convention: metadata file is CSV filename with .csv-metadata.json extension
            base = filename.rsplit('.', 1)[0]
            metadata_candidates = [
                base + '-metadata.json',
                base + '.csv-metadata.json',
                base + '.json'
            ]
            for candidate in metadata_candidates:
                if os.path.exists(candidate):
                    self.metadata_file = candidate
                    break
        
        self.metadata = {}
        self.schema = {}
        self.columns = []
        
        logging.debug('Detected delimiter %s' % (self.delimiter))
        self.reset()
    
    def _load_metadata(self):
        """Load CSVW metadata from JSON file"""
        if not self.metadata_file or not os.path.exists(self.metadata_file):
            return
        
        try:
            with open(self.metadata_file, 'r', encoding='utf-8') as f:
                self.metadata = json.load(f)
            
            # Extract table schema
            if '@context' in self.metadata:
                # This is a CSVW metadata file
                if 'tableSchema' in self.metadata:
                    self.schema = self.metadata['tableSchema']
                elif 'tables' in self.metadata and len(self.metadata['tables']) > 0:
                    # Multiple tables - use first one
                    table = self.metadata['tables'][0]
                    if 'tableSchema' in table:
                        self.schema = table['tableSchema']
                
                # Extract columns
                if 'columns' in self.schema:
                    self.columns = self.schema['columns']
                
                # Extract delimiter from dialect if present
                if 'dialect' in self.metadata:
                    dialect = self.metadata['dialect']
                    if 'delimiter' in dialect:
                        self.delimiter = dialect['delimiter']
                    if 'encoding' in dialect:
                        self.encoding = dialect['encoding']
        except Exception as e:
            logging.warning(f"Failed to load CSVW metadata: {e}")
            self.metadata = {}
            self.schema = {}
            self.columns = []
    
    @staticmethod
    def has_totals():
        """Has totals indicator"""
        return True
    
    def totals(self):
        """Returns file totals"""
        # CSVW totals should include header row (+1)
        total = rowincount(self.filename, self.fobj)
        return total + 1 if total > 0 else 1
    
    def reset(self):
        """Reset iterable"""
        super(CSVWIterable, self).reset()
        
        if self.fobj is None and self.codec is not None:
            fobj = self.codec.textIO(self.encoding)
        else:
            fobj = self.fobj
        
        self.reader = None
        self.writer = None
        self.pos = 0
        
        if self.mode == 'r':
            # Load metadata
            self._load_metadata()
            
            # Create CSV reader - use CSV headers, not metadata fieldnames
            # The CSV file has its own headers which should be used
            self.reader = csv.DictReader(fobj, delimiter=self.delimiter, quotechar=self.quotechar)
        elif self.mode in ['w', 'wr']:
            # Load metadata for writing
            self._load_metadata()
            
            # Determine fieldnames from metadata or options
            # Options are set as attributes in base class, check for 'keys' attribute
            if hasattr(self, 'keys') and self.keys:
                pass  # Already set
            elif hasattr(self, 'options') and self.options and 'keys' in self.options:
                self.keys = self.options['keys']
            elif self.columns:
                self.keys = []
                for col in self.columns:
                    if 'titles' in col and len(col['titles']) > 0:
                        self.keys.append(col['titles'][0])
                    elif 'name' in col:
                        self.keys.append(col['name'])
            else:
                self.keys = None
            
            if self.keys:
                self.writer = csv.DictWriter(fobj, fieldnames=self.keys,
                                            delimiter=self.delimiter, quotechar=self.quotechar)
                self.writer.writeheader()
            else:
                self.writer = None
    
    @staticmethod
    def id() -> str:
        return 'csvw'
    
    @staticmethod
    def is_flatonly() -> bool:
        return True
    
    def read(self, skip_empty: bool = True) -> dict:
        """Read single CSVW record"""
        row = next(self.reader)
        
        if skip_empty and len(row) == 0:
            return self.read(skip_empty)
        
        # Apply type conversions based on metadata
        if self.columns:
            converted_row = {}
            for i, (key, value) in enumerate(row.items()):
                if i < len(self.columns):
                    col_meta = self.columns[i]
                    # Get datatype from metadata
                    datatype = col_meta.get('datatype', {}).get('base', 'string')
                    
                    # Convert value based on datatype
                    if value and value != '':
                        if datatype in ['integer', 'int', 'long']:
                            try:
                                converted_row[key] = int(value)
                            except ValueError:
                                converted_row[key] = value
                        elif datatype in ['number', 'float', 'double', 'decimal']:
                            try:
                                converted_row[key] = float(value)
                            except ValueError:
                                converted_row[key] = value
                        elif datatype == 'boolean':
                            converted_row[key] = value.lower() in ('true', 't', '1', 'yes')
                        elif datatype.startswith('date') or datatype.startswith('time'):
                            # Keep as string for date/time types
                            converted_row[key] = value
                        else:
                            converted_row[key] = value
                    else:
                        converted_row[key] = None
                else:
                    converted_row[key] = value
            row = converted_row
        
        self.pos += 1
        return row
    
    def read_bulk(self, num: int = 10) -> list[dict]:
        """Read bulk CSVW records"""
        chunk = []
        for n in range(0, num):
            try:
                chunk.append(self.read())
            except StopIteration:
                break
        return chunk
    
    def write(self, record: dict):
        """Write single CSVW record"""
        if self.writer:
            self.writer.writerow(record)
        else:
            # Fallback to regular CSV writing
            if not hasattr(self, 'csv_writer'):
                self.csv_writer = csv.DictWriter(self.fobj, fieldnames=record.keys(),
                                                 delimiter=self.delimiter, quotechar=self.quotechar)
                self.csv_writer.writeheader()
            self.csv_writer.writerow(record)
        self.pos += 1
    
    def write_bulk(self, records: list[dict]):
        """Write bulk CSVW records"""
        if self.writer:
            self.writer.writerows(records)
        else:
            if not hasattr(self, 'csv_writer'):
                if records:
                    self.csv_writer = csv.DictWriter(self.fobj, fieldnames=records[0].keys(),
                                                    delimiter=self.delimiter, quotechar=self.quotechar)
                    self.csv_writer.writeheader()
            if hasattr(self, 'csv_writer'):
                self.csv_writer.writerows(records)
        self.pos += len(records)
