from __future__ import annotations
import typing
import re
from datetime import datetime

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


def parse_line_protocol(line: str) -> dict:
    """
    Parse a single InfluxDB Line Protocol line.
    
    Format: measurement[,tag_key=tag_value[,tag_key=tag_value]] field_key=field_value[,field_key=field_value] [timestamp]
    
    Returns a dict with keys: measurement, tags (dict), fields (dict), time (optional)
    """
    line = line.strip()
    if not line:
        raise ValueError("Empty line")
    
    # Parse ILP line character by character to handle quoted strings properly
    # Format: measurement[,tags] fields [timestamp]
    # Tags and fields are separated by space
    # Fields and timestamp are separated by space
    
    measurement_part = ''
    fields_part = ''
    timestamp_part = None
    
    i = 0
    in_quotes = False
    escape_next = False
    current_part = 'measurement'  # measurement, fields, timestamp
    last_space_pos = -1
    
    while i < len(line):
        char = line[i]
        
        if escape_next:
            escape_next = False
            if current_part == 'measurement':
                measurement_part += char
            elif current_part == 'fields':
                fields_part += char
            i += 1
            continue
        
        if char == '\\':
            escape_next = True
            if current_part == 'measurement':
                measurement_part += char
            elif current_part == 'fields':
                fields_part += char
            i += 1
            continue
        
        if char == '"':
            in_quotes = not in_quotes
            if current_part == 'measurement':
                measurement_part += char
            elif current_part == 'fields':
                fields_part += char
            i += 1
            continue
        
        if char == ' ' and not in_quotes:
            # Space outside quotes - might be a separator
            if current_part == 'measurement':
                # Check if next part has = (indicating fields)
                remaining = line[i+1:].strip()
                if '=' in remaining:
                    # Next part is fields
                    current_part = 'fields'
                else:
                    # Might be timestamp or just part of measurement
                    measurement_part += char
            elif current_part == 'fields':
                # Check if remaining looks like timestamp (all digits or long)
                remaining = line[i+1:].strip()
                if remaining and (remaining.isdigit() or (not '=' in remaining and len(remaining) > 10)):
                    # This is timestamp
                    timestamp_part = remaining
                    break
                else:
                    # Still part of fields (quoted string with space)
                    fields_part += char
            i += 1
            continue
        
        # Regular character
        if current_part == 'measurement':
            measurement_part += char
        elif current_part == 'fields':
            fields_part += char
        i += 1
    
    # Clean up
    measurement_part = measurement_part.strip()
    fields_part = fields_part.strip()
    if timestamp_part:
        timestamp_part = timestamp_part.strip()
    
    # Parse measurement and tags
    # Tags come after measurement, separated by commas
    # Format: measurement,tag1=val1,tag2=val2
    if ',' in measurement_part:
        # Check if there are tags (tags have = signs)
        comma_pos = measurement_part.find(',')
        # Check if there's an = sign after the first comma (indicating tags)
        if '=' in measurement_part[comma_pos+1:]:
            measurement = measurement_part[:comma_pos]
            tags_str = measurement_part[comma_pos+1:]
            
            tags = {}
            # Parse tags - split on commas, but handle escaped commas
            # Use a simple approach: split on comma, then check for escaped commas
            # For ILP, commas in tag values should be escaped, so we can split on comma
            tag_parts = tags_str.split(',')
            for tag_part in tag_parts:
                if '=' in tag_part:
                    # Split on first = (tag keys can't contain =)
                    eq_pos = tag_part.find('=')
                    if eq_pos > 0:
                        tag_key = tag_part[:eq_pos]
                        tag_value = tag_part[eq_pos+1:]
                        tags[tag_key] = unescape_value(tag_value)
        else:
            # No tags, comma is part of measurement name
            measurement = measurement_part
            tags = {}
    else:
        # No tags
        measurement = measurement_part
        tags = {}
    
    # Parse fields
    fields = {}
    if fields_part:
        # Parse fields - need to handle quoted strings properly
        # Fields are separated by commas, but quoted strings can contain commas
        i = 0
        while i < len(fields_part):
            # Find the next = sign
            eq_pos = fields_part.find('=', i)
            if eq_pos == -1:
                break
            
            field_key = fields_part[i:eq_pos]
            field_value_start = eq_pos + 1
            
            # Check if value is quoted
            if field_value_start < len(fields_part) and fields_part[field_value_start] == '"':
                # Find the closing quote (handling escaped quotes)
                quote_end = field_value_start + 1
                while quote_end < len(fields_part):
                    if fields_part[quote_end] == '"' and (quote_end == field_value_start + 1 or fields_part[quote_end - 1] != '\\'):
                        break
                    quote_end += 1
                
                field_value = fields_part[field_value_start:quote_end + 1]
                # Find next comma after the quoted value
                next_comma = fields_part.find(',', quote_end + 1)
                if next_comma == -1:
                    next_comma = len(fields_part)
                i = next_comma + 1
            else:
                # Unquoted value - find next comma
                next_comma = fields_part.find(',', field_value_start)
                if next_comma == -1:
                    field_value = fields_part[field_value_start:]
                    i = len(fields_part)
                else:
                    field_value = fields_part[field_value_start:next_comma]
                    i = next_comma + 1
            
            # Parse field value
            fields[field_key] = parse_field_value(field_value)
    
    # Parse timestamp
    time = None
    if timestamp_part:
        # Return timestamp as string (as per test expectation)
        time = str(timestamp_part)
    
    result = {
        'measurement': measurement,
        'tags': tags,
        'fields': fields
    }
    if time is not None:
        result['time'] = time
    
    return result


def unescape_value(value: str) -> str:
    """Unescape InfluxDB Line Protocol values"""
    # In ILP, commas, spaces, and equals signs need to be escaped
    # But for simplicity, we'll handle basic cases
    return value.replace('\\,', ',').replace('\\ ', ' ').replace('\\=', '=')


def parse_field_value(value: str):
    """
    Parse a field value according to InfluxDB Line Protocol rules.
    
    - String values: must be quoted with double quotes
    - Integer values: followed by 'i'
    - Float values: decimal numbers
    - Boolean values: 't', 'T', 'true', 'True', 'TRUE' for true; 'f', 'F', 'false', 'False', 'FALSE' for false
    """
    if not value:
        return None
    
    # Boolean values
    if value.lower() in ('t', 'true'):
        return True
    if value.lower() in ('f', 'false'):
        return False
    
    # Integer values (end with 'i')
    if value.endswith('i'):
        try:
            return int(value[:-1])
        except ValueError:
            pass
    
    # String values (quoted)
    if value.startswith('"') and value.endswith('"'):
        # Unescape string
        return value[1:-1].replace('\\"', '"').replace('\\n', '\n').replace('\\t', '\t')
    
    # Float values
    try:
        return float(value)
    except ValueError:
        pass
    
    # Default: return as string
    return value


def format_line_protocol(record: dict) -> str:
    """
    Format a dictionary record into InfluxDB Line Protocol format.
    
    Expected keys:
    - measurement: str (required)
    - tags: dict (optional)
    - fields: dict (required)
    - time: int or str (optional)
    """
    measurement = record.get('measurement', '')
    tags = record.get('tags', {})
    fields = record.get('fields', {})
    time = record.get('time')
    
    # Build measurement part with tags
    # Format: measurement[,tag1=val1,tag2=val2]
    measurement_with_tags = measurement
    if tags:
        tag_parts = []
        for key, value in sorted(tags.items()):
            tag_parts.append(f"{key}={escape_value(str(value))}")
        measurement_with_tags += ',' + ','.join(tag_parts)
    
    # Build fields part
    field_parts = []
    for key, value in fields.items():
        field_value = format_field_value(value)
        field_parts.append(f"{key}={field_value}")
    
    # Combine: measurement[,tags] fields [timestamp]
    line_parts = [measurement_with_tags, ','.join(field_parts)]
    
    # Add timestamp
    if time is not None:
        line_parts.append(str(time))
    
    return ' '.join(line_parts)


def escape_value(value: str) -> str:
    """Escape InfluxDB Line Protocol values"""
    return value.replace(',', '\\,').replace(' ', '\\ ').replace('=', '\\=')


def format_field_value(value) -> str:
    """Format a Python value for InfluxDB Line Protocol"""
    if isinstance(value, bool):
        return 't' if value else 'f'
    elif isinstance(value, int):
        return f"{value}i"
    elif isinstance(value, float):
        return str(value)
    elif isinstance(value, str):
        # Escape string and quote it
        escaped = value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t')
        return f'"{escaped}"'
    else:
        # Convert to string and quote
        escaped = str(value).replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t')
        return f'"{escaped}"'


class ILPIterable(BaseFileIterable):
    """InfluxDB Line Protocol iterable"""
    
    def __init__(self, filename: str = None, stream: typing.IO = None, codec: BaseCodec = None, 
                 mode: str = 'r', encoding: str = 'utf8', options: dict = {}):
        self.pos = 0
        super(ILPIterable, self).__init__(filename, stream, codec=codec, binary=False, 
                                          mode=mode, encoding=encoding, options=options)
        self.reset()
    
    @staticmethod
    def id() -> str:
        return 'ilp'
    
    @staticmethod
    def is_flatonly() -> bool:
        return False
    
    @staticmethod
    def has_totals():
        """Has totals indicator"""
        return True
    
    def totals(self):
        """Returns file totals"""
        if self.codec is not None:
            fobj = self.codec.fileobj()
        else:
            fobj = self.fobj
        return rowincount(self.filename, fobj)
    
    def reset(self):
        """Reset iterable"""
        super(ILPIterable, self).reset()
        self.pos = 0
    
    def read(self, skip_empty: bool = True) -> dict:
        """Read single ILP record"""
        while True:
            line = next(self.fobj)
            if skip_empty and not line.strip():
                continue
            self.pos += 1
            try:
                return parse_line_protocol(line)
            except (ValueError, IndexError) as e:
                if not skip_empty:
                    raise
                # Skip malformed lines if skip_empty is True
                continue
    
    def read_bulk(self, num: int = 10) -> list[dict]:
        """Read bulk ILP 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 ILP record"""
        line = format_line_protocol(record)
        self.fobj.write(line + '\n')
        self.pos += 1
    
    def write_bulk(self, records: list[dict]):
        """Write bulk ILP records"""
        for record in records:
            self.write(record)
