import logging
import os
from datetime import datetime as dt
from datetime import timedelta
from typing import List

from sqlmodel import Field, Session, SQLModel, create_engine, select, text, Relationship

logger = logging.getLogger(__name__)

DATE_FORMAT = '%Y-%m-%d'

def today():
    return dt.now().strftime(DATE_FORMAT)


class Vision(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(default=None, unique=True)
    complete_date: dt | None = Field(default=None)
    goals: list["Goal"] = Relationship(back_populates="vision")
    def __str__(self):
        return f'{self.name} (Vision {self.id})'


class PreferGoal(SQLModel, table=True):
    goal_id: int | None = Field(default=None, foreign_key="goal.id", primary_key=True)


class Goal(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(default=None, unique=True)
    start_date: dt | None = Field(default=dt.today())
    complete_date: dt | None = Field(default=None)
    vision_id: int | None = Field(default=None, foreign_key="vision.id")
    notes: str | None = Field(default=None)
    tasks: list["Task"] = Relationship(back_populates="goal")
    vision: Vision = Relationship(back_populates="goals")
    def __str__(self):
        if not self.complete_date:
            return f'{self.name} (Goal {self.id})'
        else:
            completed = self.complete_date.strftime(DATE_FORMAT)
            return f'(Done {completed}) {self.name} (Goal {self.id})'


class Task(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(default=None, unique=True)
    goal_id: int | None = Field(default=None, foreign_key="goal.id")
    complete_date: dt | None = Field(default=None)
    goal: Goal = Relationship(back_populates="tasks")
    def __str__(self):
        return f'{self.name} ({self.id})'


class Win(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    goal: str = Field(default=None, unique=False)
    vision: str | None = Field(default=None, unique=False)
    complete_date: dt | None = Field(default=dt.today())
    def __str__(self):
        completed = self.complete_date.strftime(DATE_FORMAT)
        return f'{self.goal} ({completed} {self.vision})'

def pretty_print(goal, tasks):
    results = []
    results.append(f' ## {goal}')
    if not tasks:
        results.append('-- Nothing to list --')

    for task in tasks:
        if(task.complete_date):
            results.append(f' - [x] {task}')
        else:
            results.append(f' - [ ] {task}')
    return results


def _get_with_id(cls, item_id):
    return select(cls).where(cls.id == item_id)

def _get_with_name(cls, name):
    return select(cls).where(func.lower(cls.name) == func.lower(name))


def _get_goals_by_vision(goals) -> List[str]:
    results = []
    visions = {}

    for goal in goals:
        vision = str(goal.vision)
        if not vision in visions:
            visions[vision] = []
        visions[vision].append(str(goal))
    for vision in visions:
        results.append('') # Make some space
        results.append(f'## {vision}')
        for item in visions[vision]:
            results.append(' + ' + item)
    return results


class Data():
    def __init__(self, db_file=None):
        if not db_file:
            db_file = os.path.join(os.environ.get('MATRIX_DATA_DIR', ''), 'goal.db')

        self.file = db_file
        self.engine = self.get_engine()


    def get_engine(self):
        return create_engine(f"sqlite:///{self.file}")

    def setup(self):
        SQLModel.metadata.create_all(self.engine)
        return 'Success'

    def migrate(self):
        with Session(self.engine) as session:
            session.exec(text(''' alter table goal add start_date DATETIME; '''))
            session.commit()
        return 'Success'


    def _set_pref_goal_id(self, goal_id):
        with Session(self.engine) as session:
            prefs = session.exec(select(PreferGoal)).all()
            for pref in prefs:
                session.delete(pref)
            session.commit()
            pref = PreferGoal()
            pref.goal_id = goal_id
            session.add(pref)
            session.commit()

    def _get_pref_goal_id(self):
        with Session(self.engine) as session:
            pref = session.exec(select(PreferGoal)).one()
            return pref.goal_id


    def clear(self, goal_id, clear_all=False):
        with Session(self.engine) as session:
            goal = session.exec(_get_with_id(Goal, goal_id)).first()
            for item in goal.tasks:
                if clear_all or item.complete_date:
                    session.delete(item)
            session.commit()

    def get_all_visions(self):
        with Session(self.engine) as session:
            cmd = select(Vision).where(Vision.complete_date == None)
            return session.exec(cmd).all()

    def get_all_goals(self) -> List[str]:
        '''List all goals'''
        results = []
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.complete_date == None)
            goals = session.exec(cmd).all()
            results.append(_get_goals_by_vision(goals))
        return results


    def get_current_goals(self):
        '''List current goals'''
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.complete_date == None).where(Goal.start_date <= dt.today())
            goals = session.exec(cmd).all()
            logger.debug(f'Current goals: {goals}')
            return _get_goals_by_vision(goals)

    def get_done_goals(self):
        '''List completed goals'''
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.complete_date != None)
            goals = session.exec(cmd).all()
            return _get_goals_by_vision(goals)

    def get_goal(self, goal_id):
        self._set_pref_goal_id(goal_id)
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.id == goal_id)
            goal = session.exec(cmd).first()
            return (goal, goal.tasks)

    def get_vision(self, id):
        with Session(self.engine) as session:
            cmd = select(Vision).where(Vision.id == id)
            vision = session.exec(cmd).first()
            if vision:
                return (vision, vision.goals)
            else:
                return ('Not found', [])


    def complete_vision(self, id):
        with Session(self.engine) as session:
            cmd = select(Vision).where(Vision.id == id)
            vision = session.exec(cmd).first()
            vision.complete_date = dt.today()
            session.commit()


    def complete_goal(self, goal_id):
        vision_id = -1
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.id == goal_id)
            goal = session.exec(cmd).first()
            goal.complete_date = dt.today()
            vision_id = goal.vision_id

            cmd = select(Vision).where(Vision.id == vision_id)
            win = Win()
            win.goal = goal.name

            vision = session.exec(cmd).first()
            if vision:
                win.vision = vision.name

            session.add(win)
            session.commit()
        return vision_id


    def put_off_vision(self, vision_id):
        with Session(self.engine) as session:
            # vision = session.exec(cmd).first()
            cmd = select(Goal).where(Goal.vision_id == vision_id)
            goals = session.exec(cmd).all()
            for goal in goals:
                goal.start_date = dt.today() + timedelta(days=14)
            session.commit()
        return True

    def start_goal(self, goal_id):
        vision = None
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.id == goal_id)
            goal = session.exec(cmd).first()
            goal.start_date = dt.today()
            vision_id = goal.vision_id
            session.commit()
        if vision:
            return (vision, vision.goals)
        else:
            return ('No Vision Assigned', [])


    def rename_goal(self, goal_id, name):
        self._set_pref_goal_id(goal_id)
        vision_id = -1
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.id == goal_id)
            goal = session.exec(cmd).first()
            goal.name = name
            vision_id = goal.vision_id
            session.commit()
        return vision_id


    def complete_task(self, id):
        with Session(self.engine) as session:
            cmd = select(Task).where(Task.id == id)
            task = session.exec(cmd).first()
            task.complete_date = dt.today()
            goal_id = task.goal_id
            session.commit()
        self._set_pref_goal_id(goal_id)
        return goal_id

    def add_with_name(self, cls, name):
        id = -1
        with Session(self.engine) as session:
            new = cls(name=name)
            session.add(new)
            session.commit()
            id = new.id
        return id

    def add_vision(self, name):
        return self.add_with_name(Vision, name)
            
    def add_goal(self, name):
        goal_id = self.add_with_name(Goal, name)
        self._set_pref_goal_id(goal_id)
        return goal_id

    def quick_add_task(self, name):
        goal_id = self._get_pref_goal_id()
        task_id = self.add_with_name(Task, name)
        with Session(self.engine) as session:
            cmd = select(Task).where(Task.id == task_id)
            new = session.exec(cmd).first()
            new.goal_id = goal_id
            session.commit()
        return goal_id

    def remove_task(self, task_id):
        task_name = ''
        logger.debug(f'Removing task with id {task_id}')
        with Session(self.engine) as session:
            cmd = select(Task).where(Task.id  == int(task_id))
            to_remove = session.exec(cmd).first()
            if not to_remove:
                return f'Unable to find task with id {task_id}'

            logger.debug(f'Removing task with name {to_remove.name}')
            task_name = to_remove.name
            session.delete(to_remove)
            session.commit()
        return f'Removed task "{task_name}"'

    def assign_vision(self, goal_id, vision_id):
        '''Goals may move between visions'''
        self._set_pref_goal_id(goal_id)
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.id == goal_id)
            goal = session.exec(cmd).first()
            goal.vision_id = vision_id
            session.commit()
        return True

    def visionless(self):
        '''Get goals with no vision'''
        with Session(self.engine) as session:
            cmd = select(Goal).where(Goal.vision_id  == None).where(Goal.complete_date == None)
            return session.exec(cmd).all()


    def add_note(self, note, goal):
        '''Notes print with the goal.'''
        pass


    def wins(self):
        results = []
        with Session(self.engine) as session:
            results = session.exec(select(Win)).all()
        return results

    # Someday: Notes on each

