#!/usr/bin/env python
# coding: utf-8



URL = 'https://manage.jaen.kr'


import requests
import json
import datetime
import os
import pandas as pd
from requests.auth import HTTPBasicAuth
from IPython.display import display

class Dataset:
  
    def __init__(self, data_dir='data'):
        self.data_dir = data_dir
        # data 폴더 생성
        if not os.path.exists(data_dir):
            os.makedirs(data_dir)
        try:
            res = requests.get(URL+'/api/project/list_data')
            
        except requests.exceptions.Timeout as errd:
            print("Timeout Error : ", errd)
            
        except requests.exceptions.ConnectionError as errc:
            print("Error Connecting : ", errc)
            
        except requests.exceptions.HTTPError as errb:
            print("Http Error : ", errb)

        # Any Error except upper exception
        except requests.exceptions.RequestException as erra:
            print("AnyException : ", erra)

        if res.status_code == 200:
            project_data = json.loads(res.text)
            # project_data 변수로부터 데이터를 처리
        else:
            print("API에서 데이터를 가져오지 못했습니다. 상태 코드:", res.status_code)
            print("요청된 값:", json.loads(res.text))

        ids = []
        titles = []
        filenames = list(project_data['data_file'].values())
        infos = list(project_data['data_info'].values())
        datas = []
        for e in range(len(list(project_data['data_info'].keys()))):
            id_val = list(project_data['data_info'].keys())[e].split('_')[0]
            ids.append(id_val)
            titles.append((list(project_data['data_info'].keys())[e].split('_')[1]))
            if len(filenames[e])==1:
                datas.append(URL + f'/api/project/dataSingleDownload?pro_id={id_val}&fileName={str(filenames[e])[2:-2]}')
            else:
                temp = []
                for i in range(len(filenames[e])):
                    temp.append(URL + f'/api/project/dataSingleDownload?pro_id={id_val}&fileName={str(filenames[e][i])}')
                datas.append(temp)

        self.dataset = pd.DataFrame({
            'pro_id': ids,
            'name': titles,
            'info': infos,
            'data': datas,
            'filename': filenames,
        })


    def info(self):
        display(self.dataset[['pro_id', 'name', 'info', 'filename']])


    def load(self, dataset_names):
        global datasets
        username = 'mysuni'
        password = 'mysuni1!'

        if type(dataset_names) == str:
            df = self.dataset.loc[self.dataset['name'] == dataset_names]
            if df.shape[0] > 0:
                fileurl = df['data']
                filename = df['filename']
                if type(fileurl.iloc[0]) == str:
                    fileurl.iloc[0] = pd.Series(fileurl.iloc[0])
                for f_name, f_url in zip(filename.iloc[0], fileurl.iloc[0]):
                    r = requests.get(f_url, auth=HTTPBasicAuth(username, password))
                    filepath = os.path.join(self.data_dir, f_name)
                    open(filepath, 'wb').write(r.content)
                    print(f'파일 다운로드 완료\n====================\n\n데이터셋: {dataset_names}\n파일경로: {filepath}\n\n====================')
                return
            else:
                raise Exception('데이터셋 정보가 없습니다.')

        elif type(dataset_names) == list or type(dataset_names) == tuple:
            for dataset_name in dataset_names:
                df = self.dataset.loc[self.dataset['name'] == dataset_name]
                if df.shape[0] > 0:
                    fileurls = df['data'].iloc[0]
                    filenames = df['filename'].iloc[0]
                    if type(fileurls) == str:
                        fileurls = pd.Series(fileurls)
                    for fileurl, filename in zip(fileurls, filenames):
                        r = requests.get(fileurl, auth=HTTPBasicAuth(username, password))
                        filepath = os.path.join(self.data_dir, filename)
                        open(filepath, 'wb').write(r.content)
                        print(f'파일 다운로드 완료\n====================\n\n데이터셋: {dataset_name}\n파일경로: {filepath}\n\n====================')
                else:
                    raise Exception('데이터셋 정보가 없습니다.')
            return
        else:
            raise Exception('잘못된 정보입니다.')
            

dataset = Dataset()


def list_data():
    global dataset
    dataset.info()


def download_data(dataset_name):
    global dataset
    return dataset.load(dataset_name)





class Project:
    def __init__(self, project_name, class_info, email, server='manage'):
        self.project_name = project_name
        self.edu_name = class_info['edu_name']
        self.edu_rnd = class_info['edu_rnd']
        self.edu_class = class_info['edu_class']
        self.email = email
        self.server = server

    def __make_submission(self, submission):
        timestring = datetime.datetime.now().strftime('%H-%M-%S')
        filename = 'submission-{}.csv'.format(timestring)
        submission.to_csv(filename, index=False)
        print('파일을 저장하였습니다. 파일명: {}'.format(filename))
        return filename

    def __project_submission(self, file_name):
        file_path = './'
        url = URL + f'/api/studentProject/apiScoring?edu_name={self.edu_name}&edu_rnd={self.edu_rnd}&edu_class={self.edu_class}&mail={self.email}&project_name={self.project_name}&file_name={file_name}'
        files = {'file': (file_name, open(file_path + file_name, 'rb'), 'text/csv')}
        r = requests.post(url, files=files)
        r.encoding = 'utf-8'
        message = ''
        try:
            data = json.loads(r.text) # json 변환 실패시 원본 메세지 사용
            if 'trial' in data.keys():
                message = '제출 여부 :{}\n오늘 제출 횟수 : {}\n제출 결과:{}'.format(data['msg'], data['trial'], data['score'])
            else:
                message = '제출 실패 : {}'.format(data['msg'])
        except:
            message = '변환 에러 발생 : {}'.format(r.text)
        return message

    def submit_ipynb(self, ipynb_file_path=None):
        if ipynb_file_path is None:
            raise Exception('노트북(ipynb) 파일의 경로를 입력해 주세요.') 
        url = URL + '/api/studentProject/ipynbUploadFromMod'
        upload = {'upload_file': open(ipynb_file_path, 'rb')}
        upload['filename'] = upload['upload_file'].name[2:]
        res = requests.post(url, data = upload, params = self.info(), verify = False)
        print(res.text)

    def submit(self, submission):
        filename = self.__make_submission(submission)
        print(self.__project_submission(filename))
    
    def info(self):
        return {'edu_name': self.edu_name, 
                'edu_rnd': self.edu_rnd,
                'edu_class': self.edu_class, 
                'email': self.email, 
                'project_name': self.project_name}

def submit(submission_file):
    global project
    project.submit(submission_file)

def submit_ipynb(ipynb_file_path):
    global project
    project.submit_ipynb(ipynb_file_path)


def update_project(project_name=None, class_info=None, email=None):
    global project
    if project_name:
        project.project_name = project_name
    if project.class_info:
        project.class_info = class_info
    if project.email:
        project.email = email
    print('정보 업데이트 완료')


import torch
import torch.nn as nn
from torchvision import datasets, transforms
import requests
import inspect

class Exam:
    BASE_URL = "https://sec.baem1n.com"

    def __init__(self, student_name, class_name, class_round, exam_type):
        self.student_name = student_name
        self.class_name = class_name
        self.class_round = class_round
        self.exam_type = exam_type

    @staticmethod
    def extract_code(func):
        """함수나 클래스의 소스 코드를 추출"""
        return inspect.getsource(func)

    @staticmethod
    def extract_dataloader_info(dataloader):
        """DataLoader의 batch_size와 shuffle 속성을 추출"""
        return {
            "batch_size": dataloader.batch_size,
            "shuffle": dataloader.shuffle
        }

    @staticmethod
    def extract_model_info(model):
        """CNN 또는 RNN 모델의 계층 정보 추출"""
        layers_info = []
        for layer in model.children():
            if isinstance(layer, nn.Conv2d):
                layers_info.append({
                    "type": "Conv2d",
                    "in_channels": layer.in_channels,
                    "out_channels": layer.out_channels,
                    "kernel_size": layer.kernel_size,
                    "padding": layer.padding
                })
            elif isinstance(layer, nn.MaxPool2d):
                layers_info.append({"type": "MaxPool2d", "kernel_size": layer.kernel_size})
            elif isinstance(layer, nn.Linear):
                layers_info.append({
                    "type": "Linear",
                    "in_features": layer.in_features,
                    "out_features": layer.out_features
                })
            elif isinstance(layer, nn.LSTM):
                layers_info.append({
                    "type": "LSTM",
                    "input_size": layer.input_size,
                    "hidden_size": layer.hidden_size
                })
        return layers_info

    def evaluate_model(self, model, test_loader):
        """MNIST 테스트셋에서 제출된 모델의 성능을 평가"""
        criterion = nn.CrossEntropyLoss()
        device = torch.device('cpu')
        model.to(device)
        model.eval()

        total_loss = 0
        correct = 0
        with torch.no_grad():
            for data, target in test_loader:
                data, target = data.to(device), target.to(device)
                output = model(data)
                total_loss += criterion(output, target).item()
                pred = output.argmax(dim=1, keepdim=True)
                correct += pred.eq(target.view_as(pred)).sum().item()

        avg_loss = total_loss / len(test_loader.dataset)
        accuracy = correct / len(test_loader.dataset)
        return accuracy, avg_loss

    def submit_to_server(self, question_id, payload):
        """서버로 데이터를 전송"""
        url = f"{self.BASE_URL}/submit_question_{question_id}/"
        response = requests.post(url, json=payload)
        if response.status_code == 200:
            return "제출 성공"
        return f"제출 실패: {response.status_code}"

    def submit(self, question_id, answer=None, dataset_class=None, dataloader=None, model=None):
        """문제 제출 메소드"""
        payload = {
            "student_id": self.student_name,
            "class_name":   self.class_name,
            "class_round":  self.class_round,
            "exam_type":   self.exam_type,
         }
        
        if question_id == 1 and answer is not None:  # 함수 구현 제출
            payload["code"] = self.extract_code(answer)

        elif question_id == 2 and dataset_class is not None and dataloader is not None:  # 데이터셋 및 데이터로더 제출
            payload["dataset_code"] = self.extract_code(dataset_class)
            payload["dataloader_info"] = self.extract_dataloader_info(dataloader)

        elif question_id == 3 and model is not None:  # CNN 모델 제출
            payload["model_info"] = self.extract_model_info(model)

        elif question_id == 4 and model is not None:  # LSTM 모델 제출
            payload["model_info"] = self.extract_model_info(model)

        elif question_id == 5 and model is not None:  # DNN 모델 성능 평가 후 제출
            torch.manual_seed(42)
            transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
            test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
            test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)

            accuracy, loss = self.evaluate_model(model, test_loader)
            payload["accuracy"] = accuracy
            payload["loss"] = loss

        else:
            return "지원되지 않는 문제이거나 잘못된 입력입니다."

        return self.submit_to_server(question_id, payload)
