import fcntl
import json
import logging
import os
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional

from arkeo.path import PathManager
from utils.alfnu import increment
from utils.json import load_json


log = logging.getLogger(__name__)


FILE_PK = "pks.json"
FILE_PK_LOG = "pks.jsonl"
FILE_SEQ = "pk_seq.json"


def format_nav_key(date: datetime, key: str) -> str:
    return f"{date.year}-{date.month:02d}-{key}"


def format_nav_path(nav_key: str) -> str:
    """format navigation path: yyyy-mm-zzz → yyyy/mm/zzz"""
    return nav_key.replace("-", "/")


def is_nav_key_format(s):
    """YYYY-mm-ZZZ"""
    return (
        isinstance(s, str)
        and len(s) == 11
        and s[4] == "-"
        and s[7] == "-"
        and s[:4].isdigit()
        and s[5:7].isdigit()
        and all(c.isdigit() or c.isupper() for c in s[8:])
    )


class PrimaryKey:

    def __init__(self, path_manager: Optional[PathManager] = None):
        self.path = path_manager or PathManager()
        self.drive = self.path.drive
        self._pks = None

    @property
    def pks(self) -> Dict[str, Any]:
        if not self._pks:
            self._load_pks()

        return self._pks

    @property
    def pk_path(self) -> Path:
        return Path(self.path.indexes) / FILE_PK

    @property
    def pk_log_path(self) -> Path:
        return Path(self.path.system) / FILE_PK_LOG

    @property
    def seq_path(self) -> Path:
        return Path(self.path.system) / FILE_SEQ

    def get_nav_key(self, pk: str) -> Optional[str]:
        grep_result = subprocess.run(
            ["grep", f'^{{"{pk}":', self.pk_log_path], capture_output=True, text=True
        )

        if grep_result.stdout:
            line = grep_result.stdout.strip()
            record = json.loads(line)
            nav_key = record[pk]["nav_key"]
            return nav_key
        return None

    def get_next_pk(self) -> str:
        if self.path.use_google_drive:
            data = load_json(self.drive, self.seq_path)
            new_key = self._update_sequence_data(data)
            self.drive.write(self.seq_path, json.dumps(data, indent=2))
            return new_key
        else:
            with open(self.seq_path, "r+") as f:
                fcntl.flock(f.fileno(), fcntl.LOCK_EX)
                try:
                    content = f.read()
                    data = json.loads(content) if content else {}
                    new_key = self._update_sequence_data(data)
                except (json.JSONDecodeError, ValueError):
                    data = {}

                f.seek(0)
                json.dump(data, f, indent=2)
                f.truncate()

                return new_key

    def update_pk(self, key: str, value: Dict[str, str]) -> None:
        """insert new key before closing brace"""
        if self.drive.gd:
            data = load_json(self.drive, self.pk_log_path)
            data[key] = value
            self.drive.write(self.pk_log_path, json.dumps(data, indent=2))
        else:
            with open(self.pk_log_path, "a") as f:  # 'a' for append!
                fcntl.flock(f.fileno(), fcntl.LOCK_EX)
                f.write(json.dumps({key: value}) + "\n")
                f.flush()
                os.fsync(f.fileno())

        self._pks = None

    def _load_pks(self):
        try:
            # compare timestamps
            subprocess.run(["test", self.pk_path, "-nt", self.pk_log_path], check=True)
            self._pks = load_json(self.drive, self.pk_path)
        except subprocess.CalledProcessError:
            self._reload_pks()

    def _reload_pks(self):
        """rebuild JSON cache using subprocess commands"""
        # method 1: jq
        try:
            # convert JSONL to JSON using jq: read lines, parse each, merge into single object
            cmd = ["jq", "-s", "add", self.pk_log_path]
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            self._pks = json.loads(result.stdout)
            self.drive.write(self.pk_path, result.stdout)
            return
        except (subprocess.CalledProcessError, FileNotFoundError):
            # jq not available, fall back to awk/sed approach
            pass

        # method 2: awk
        awk_script = """
    BEGIN { print "{" }
    {
      gsub(/^{/, "", $0);
      gsub(/}$/, "", $0);
      if (NR > 1) print ",";
      printf "%s", $0;
    }
    END { print "}" }
    """

        try:
            # remove outer braces and combine into single JSON object
            cmd = ["awk", awk_script, self.pk_log_path]
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            self._pks = json.loads(result.stdout)
            self.drive.write(self.pk_path, result.stdout)
            return
        except subprocess.CalledProcessError:
            pass

        # method 3: sed
        try:
            # remove first and last braces, add commas, wrap in outer braces
            cmd = f"""sed 's/^{{//; s/}}$//' {self.pk_log_path} | sed '$!s/$/,/' | sed '1i{{' | sed '$a}}\' """
            result = subprocess.run(
                cmd, shell=True, capture_output=True, text=True, check=True
            )
            self._pks = json.loads(result.stdout)
            self.drive.write(self.pk_path, result.stdout)
            return
        except subprocess.CalledProcessError:
            pass

        # method 4: python
        all_data = {}
        with open(self.pk_log_path, "r") as f:
            for line in f:
                if line.strip():
                    record = json.loads(line)
                    all_data.update(record)

        self._pks = all_data
        self.drive.write(self.pk_path, json.dumps(all_data))
        return

    def _update_sequence_data(self, data: Dict[str, Any]) -> str:
        max_key = data.get("max_key", "000")
        new_key = increment(max_key)
        data["max_key"] = new_key
        data["last_updated"] = datetime.now().isoformat()
        return new_key
